opencode-mem 1.0.0 → 2.0.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 +80 -477
- package/dist/config.d.ts +5 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +46 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -88
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +1 -8
- package/dist/services/ai/ai-provider-factory.d.ts +8 -0
- package/dist/services/ai/ai-provider-factory.d.ts.map +1 -0
- package/dist/services/ai/ai-provider-factory.js +25 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +13 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts.map +1 -0
- package/dist/services/ai/providers/anthropic-messages.js +176 -0
- package/dist/services/ai/providers/base-provider.d.ts +21 -0
- package/dist/services/ai/providers/base-provider.d.ts.map +1 -0
- package/dist/services/ai/providers/base-provider.js +6 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +12 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-chat-completion.js +181 -0
- package/dist/services/ai/providers/openai-responses.d.ts +14 -0
- package/dist/services/ai/providers/openai-responses.d.ts.map +1 -0
- package/dist/services/ai/providers/openai-responses.js +191 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +21 -0
- package/dist/services/ai/session/ai-session-manager.d.ts.map +1 -0
- package/dist/services/ai/session/ai-session-manager.js +165 -0
- package/dist/services/ai/session/session-types.d.ts +43 -0
- package/dist/services/ai/session/session-types.d.ts.map +1 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +41 -0
- package/dist/services/ai/tools/tool-schema.d.ts.map +1 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/api-handlers.d.ts +11 -3
- package/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +143 -30
- package/dist/services/auto-capture.d.ts +1 -30
- package/dist/services/auto-capture.d.ts.map +1 -1
- package/dist/services/auto-capture.js +199 -396
- package/dist/services/cleanup-service.d.ts +3 -0
- package/dist/services/cleanup-service.d.ts.map +1 -1
- package/dist/services/cleanup-service.js +31 -4
- package/dist/services/client.d.ts +1 -0
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/client.js +3 -11
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
- package/dist/services/sqlite/connection-manager.js +8 -4
- package/dist/services/user-memory-learning.d.ts +3 -0
- package/dist/services/user-memory-learning.d.ts.map +1 -0
- package/dist/services/user-memory-learning.js +157 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +38 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts.map +1 -0
- package/dist/services/user-prompt/user-prompt-manager.js +164 -0
- package/dist/services/web-server-worker.js +27 -6
- package/dist/services/web-server.d.ts.map +1 -1
- package/dist/services/web-server.js +0 -5
- package/dist/types/index.d.ts +5 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/web/app.js +210 -120
- package/dist/web/index.html +14 -10
- package/dist/web/styles.css +326 -1
- package/package.json +4 -1
- package/dist/services/compaction.d.ts +0 -92
- package/dist/services/compaction.d.ts.map +0 -1
- package/dist/services/compaction.js +0 -421
- package/dist/services/sqlite-client.d.ts +0 -116
- package/dist/services/sqlite-client.d.ts.map +0 -1
- package/dist/services/sqlite-client.js +0 -284
- package/dist/services/web-server-lock.d.ts +0 -12
- package/dist/services/web-server-lock.d.ts.map +0 -1
- package/dist/services/web-server-lock.js +0 -157
- package/dist/web/favicon.svg +0 -14
|
@@ -35,14 +35,12 @@ export class WebServer {
|
|
|
35
35
|
const response = event.data;
|
|
36
36
|
if (response.type === "started") {
|
|
37
37
|
this.isOwner = true;
|
|
38
|
-
log("Web server started (owner)", { url: response.url });
|
|
39
38
|
resolve();
|
|
40
39
|
}
|
|
41
40
|
else if (response.type === "error") {
|
|
42
41
|
const errorMsg = response.error || "Unknown error";
|
|
43
42
|
if (errorMsg.includes("EADDRINUSE") || errorMsg.includes("address already in use")) {
|
|
44
43
|
this.isOwner = false;
|
|
45
|
-
log("Web server already running (port in use)");
|
|
46
44
|
resolve();
|
|
47
45
|
}
|
|
48
46
|
else {
|
|
@@ -76,7 +74,6 @@ export class WebServer {
|
|
|
76
74
|
}
|
|
77
75
|
async stop() {
|
|
78
76
|
if (!this.isOwner || !this.worker) {
|
|
79
|
-
log("Web server stop skipped (not owner or no worker)");
|
|
80
77
|
return;
|
|
81
78
|
}
|
|
82
79
|
return new Promise((resolve) => {
|
|
@@ -85,7 +82,6 @@ export class WebServer {
|
|
|
85
82
|
this.worker.terminate();
|
|
86
83
|
this.worker = null;
|
|
87
84
|
}
|
|
88
|
-
log("Web server stopped (timeout, forced termination)");
|
|
89
85
|
resolve();
|
|
90
86
|
}, 5000);
|
|
91
87
|
this.worker.onmessage = (event) => {
|
|
@@ -96,7 +92,6 @@ export class WebServer {
|
|
|
96
92
|
this.worker.terminate();
|
|
97
93
|
this.worker = null;
|
|
98
94
|
}
|
|
99
|
-
log("Web server stopped (owner exiting)");
|
|
100
95
|
resolve();
|
|
101
96
|
}
|
|
102
97
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -32,11 +32,14 @@ export interface ConversationIngestResponse {
|
|
|
32
32
|
}
|
|
33
33
|
export interface MemoryMetadata {
|
|
34
34
|
type?: MemoryType;
|
|
35
|
-
source?: "manual" | "auto-capture" | "import" | "api";
|
|
35
|
+
source?: "manual" | "auto-capture" | "import" | "api" | "user-learning";
|
|
36
36
|
tool?: string;
|
|
37
37
|
sessionID?: string;
|
|
38
38
|
reasoning?: string;
|
|
39
39
|
captureTimestamp?: number;
|
|
40
|
+
promptCount?: number;
|
|
41
|
+
analysisTimestamp?: number;
|
|
42
|
+
promptId?: string;
|
|
40
43
|
displayName?: string;
|
|
41
44
|
userName?: string;
|
|
42
45
|
userEmail?: string;
|
|
@@ -45,4 +48,5 @@ export interface MemoryMetadata {
|
|
|
45
48
|
gitRepoUrl?: string;
|
|
46
49
|
[key: string]: unknown;
|
|
47
50
|
}
|
|
51
|
+
export type AIProviderType = "openai-chat" | "openai-responses" | "anthropic";
|
|
48
52
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExE,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAAC;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExE,MAAM,MAAM,uBAAuB,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAAC;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,GAAG,eAAe,CAAC;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,kBAAkB,GAAG,WAAW,CAAC"}
|
package/dist/web/app.js
CHANGED
|
@@ -8,13 +8,25 @@ const state = {
|
|
|
8
8
|
totalPages: 1,
|
|
9
9
|
totalItems: 0,
|
|
10
10
|
selectedTag: "",
|
|
11
|
-
|
|
11
|
+
currentScope: "project",
|
|
12
12
|
searchQuery: "",
|
|
13
13
|
isSearching: false,
|
|
14
14
|
selectedMemories: new Set(),
|
|
15
15
|
autoRefreshInterval: null,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
marked.setOptions({
|
|
19
|
+
gfm: true,
|
|
20
|
+
breaks: true,
|
|
21
|
+
headerIds: false,
|
|
22
|
+
mangle: false,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function renderMarkdown(markdown) {
|
|
26
|
+
const html = marked.parse(markdown);
|
|
27
|
+
return DOMPurify.sanitize(html);
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
async function fetchAPI(endpoint, options = {}) {
|
|
19
31
|
try {
|
|
20
32
|
const response = await fetch(API_BASE + endpoint, options);
|
|
@@ -41,21 +53,21 @@ function populateTagDropdowns() {
|
|
|
41
53
|
tagFilter.innerHTML = '<option value="">All Tags</option>';
|
|
42
54
|
addTag.innerHTML = '<option value="">Select tag</option>';
|
|
43
55
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
const scopeTags = state.currentScope === "user" ? state.tags.user : state.tags.project;
|
|
57
|
+
|
|
58
|
+
scopeTags.forEach((tagInfo) => {
|
|
47
59
|
const displayText = tagInfo.displayName || tagInfo.tag;
|
|
48
60
|
const shortDisplay =
|
|
49
61
|
displayText.length > 50 ? displayText.substring(0, 50) + "..." : displayText;
|
|
50
62
|
|
|
51
63
|
const option1 = document.createElement("option");
|
|
52
64
|
option1.value = tagInfo.tag;
|
|
53
|
-
option1.textContent =
|
|
65
|
+
option1.textContent = shortDisplay;
|
|
54
66
|
tagFilter.appendChild(option1);
|
|
55
67
|
|
|
56
68
|
const option2 = document.createElement("option");
|
|
57
69
|
option2.value = tagInfo.tag;
|
|
58
|
-
option2.textContent =
|
|
70
|
+
option2.textContent = shortDisplay;
|
|
59
71
|
addTag.appendChild(option2);
|
|
60
72
|
});
|
|
61
73
|
}
|
|
@@ -63,7 +75,7 @@ function populateTagDropdowns() {
|
|
|
63
75
|
async function loadMemories() {
|
|
64
76
|
showRefreshIndicator(true);
|
|
65
77
|
|
|
66
|
-
let endpoint = `/api/memories?page=${state.currentPage}&pageSize=${state.pageSize}`;
|
|
78
|
+
let endpoint = `/api/memories?page=${state.currentPage}&pageSize=${state.pageSize}&scope=${state.currentScope}&includePrompts=true`;
|
|
67
79
|
|
|
68
80
|
if (state.isSearching && state.searchQuery) {
|
|
69
81
|
endpoint = `/api/search?q=${encodeURIComponent(state.searchQuery)}&page=${state.currentPage}&pageSize=${state.pageSize}`;
|
|
@@ -103,66 +115,12 @@ function renderMemories() {
|
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
container.innerHTML = state.memories
|
|
106
|
-
.map((
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
? `<span class="similarity-score">${memory.similarity}%</span>`
|
|
112
|
-
: "";
|
|
113
|
-
|
|
114
|
-
let displayInfo = memory.displayName || memory.id;
|
|
115
|
-
if (memory.scope === "project" && memory.projectPath) {
|
|
116
|
-
const pathParts = memory.projectPath.split("/");
|
|
117
|
-
displayInfo = pathParts[pathParts.length - 1] || memory.projectPath;
|
|
118
|
+
.map((item) => {
|
|
119
|
+
if (item.type === "prompt") {
|
|
120
|
+
return renderPromptCard(item);
|
|
121
|
+
} else {
|
|
122
|
+
return renderMemoryCard(item);
|
|
118
123
|
}
|
|
119
|
-
|
|
120
|
-
let subtitle = "";
|
|
121
|
-
if (memory.scope === "user" && memory.userEmail) {
|
|
122
|
-
subtitle = `<span class="memory-subtitle">${escapeHtml(memory.userEmail)}</span>`;
|
|
123
|
-
} else if (memory.scope === "project" && memory.projectPath) {
|
|
124
|
-
subtitle = `<span class="memory-subtitle">${escapeHtml(memory.projectPath)}</span>`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const pinButton = isPinned
|
|
128
|
-
? `<button class="btn-pin pinned" onclick="unpinMemory('${memory.id}')" title="Unpin"><i data-lucide="pin" class="icon icon-filled"></i></button>`
|
|
129
|
-
: `<button class="btn-pin" onclick="pinMemory('${memory.id}')" title="Pin"><i data-lucide="pin" class="icon"></i></button>`;
|
|
130
|
-
|
|
131
|
-
const createdDate = formatDate(memory.createdAt);
|
|
132
|
-
const updatedDate =
|
|
133
|
-
memory.updatedAt && memory.updatedAt !== memory.createdAt
|
|
134
|
-
? formatDate(memory.updatedAt)
|
|
135
|
-
: null;
|
|
136
|
-
|
|
137
|
-
const dateInfo = updatedDate
|
|
138
|
-
? `<span>Created: ${createdDate}</span><span>Updated: ${updatedDate}</span>`
|
|
139
|
-
: `<span>Created: ${createdDate}</span>`;
|
|
140
|
-
|
|
141
|
-
return `
|
|
142
|
-
<div class="memory-card ${isSelected ? "selected" : ""} ${isPinned ? "pinned" : ""}" data-id="${memory.id}">
|
|
143
|
-
<div class="memory-header">
|
|
144
|
-
<div class="meta">
|
|
145
|
-
<input type="checkbox" class="memory-checkbox" data-id="${memory.id}" ${isSelected ? "checked" : ""} />
|
|
146
|
-
<span class="badge badge-${memory.scope}">${memory.scope}</span>
|
|
147
|
-
${memory.type ? `<span class="badge badge-type">${memory.type}</span>` : ""}
|
|
148
|
-
${similarityHtml}
|
|
149
|
-
${isPinned ? '<span class="badge badge-pinned">PINNED</span>' : ""}
|
|
150
|
-
<span class="memory-display-name">${escapeHtml(displayInfo)}</span>
|
|
151
|
-
${subtitle}
|
|
152
|
-
</div>
|
|
153
|
-
<div class="memory-actions">
|
|
154
|
-
${pinButton}
|
|
155
|
-
<button class="btn-edit" onclick="editMemory('${memory.id}')"><i data-lucide="edit-3" class="icon"></i></button>
|
|
156
|
-
<button class="btn-delete" onclick="deleteMemory('${memory.id}')"><i data-lucide="trash-2" class="icon"></i></button>
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
<div class="memory-content">${escapeHtml(memory.content)}</div>
|
|
160
|
-
<div class="memory-footer">
|
|
161
|
-
${dateInfo}
|
|
162
|
-
<span>ID: ${memory.id}</span>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
`;
|
|
166
124
|
})
|
|
167
125
|
.join("");
|
|
168
126
|
|
|
@@ -173,6 +131,102 @@ function renderMemories() {
|
|
|
173
131
|
lucide.createIcons();
|
|
174
132
|
}
|
|
175
133
|
|
|
134
|
+
function renderPromptCard(prompt) {
|
|
135
|
+
const isLinked = !!prompt.linkedMemoryId;
|
|
136
|
+
const isSelected = state.selectedMemories.has(prompt.id);
|
|
137
|
+
const promptDate = formatDate(prompt.createdAt);
|
|
138
|
+
|
|
139
|
+
return `
|
|
140
|
+
<div class="prompt-card ${isSelected ? "selected" : ""}" data-id="${prompt.id}">
|
|
141
|
+
<div class="prompt-header">
|
|
142
|
+
<div class="meta">
|
|
143
|
+
<input type="checkbox" class="memory-checkbox" data-id="${prompt.id}" ${isSelected ? "checked" : ""} />
|
|
144
|
+
<i data-lucide="message-circle" class="icon"></i>
|
|
145
|
+
<span class="badge badge-prompt">USER PROMPT</span>
|
|
146
|
+
${isLinked ? '<span class="badge badge-linked">🔗 LINKED</span>' : ""}
|
|
147
|
+
<span class="prompt-date">${promptDate}</span>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="prompt-actions">
|
|
150
|
+
<button class="btn-delete" onclick="deletePromptWithLink('${prompt.id}', ${isLinked})">
|
|
151
|
+
<i data-lucide="trash-2" class="icon"></i>
|
|
152
|
+
${isLinked ? "Delete Pair" : "Delete"}
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="prompt-content">
|
|
157
|
+
${escapeHtml(prompt.content)}
|
|
158
|
+
</div>
|
|
159
|
+
${isLinked ? '<div class="link-indicator">↓ Generated memory above ↑</div>' : ""}
|
|
160
|
+
</div>
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function renderMemoryCard(memory) {
|
|
165
|
+
const isSelected = state.selectedMemories.has(memory.id);
|
|
166
|
+
const isPinned = memory.isPinned || false;
|
|
167
|
+
const isLinked = !!memory.linkedPromptId;
|
|
168
|
+
const similarityHtml =
|
|
169
|
+
memory.similarity !== undefined
|
|
170
|
+
? `<span class="similarity-score">${memory.similarity}%</span>`
|
|
171
|
+
: "";
|
|
172
|
+
|
|
173
|
+
let displayInfo = memory.displayName || memory.id;
|
|
174
|
+
if (memory.scope === "project" && memory.projectPath) {
|
|
175
|
+
const pathParts = memory.projectPath.split("/");
|
|
176
|
+
displayInfo = pathParts[pathParts.length - 1] || memory.projectPath;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let subtitle = "";
|
|
180
|
+
if (memory.scope === "user" && memory.userEmail) {
|
|
181
|
+
subtitle = `<span class="memory-subtitle">${escapeHtml(memory.userEmail)}</span>`;
|
|
182
|
+
} else if (memory.scope === "project" && memory.projectPath) {
|
|
183
|
+
subtitle = `<span class="memory-subtitle">${escapeHtml(memory.projectPath)}</span>`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const pinButton = isPinned
|
|
187
|
+
? `<button class="btn-pin pinned" onclick="unpinMemory('${memory.id}')" title="Unpin"><i data-lucide="pin" class="icon icon-filled"></i></button>`
|
|
188
|
+
: `<button class="btn-pin" onclick="pinMemory('${memory.id}')" title="Pin"><i data-lucide="pin" class="icon"></i></button>`;
|
|
189
|
+
|
|
190
|
+
const createdDate = formatDate(memory.createdAt);
|
|
191
|
+
const updatedDate =
|
|
192
|
+
memory.updatedAt && memory.updatedAt !== memory.createdAt ? formatDate(memory.updatedAt) : null;
|
|
193
|
+
|
|
194
|
+
const dateInfo = updatedDate
|
|
195
|
+
? `<span>Created: ${createdDate}</span><span>Updated: ${updatedDate}</span>`
|
|
196
|
+
: `<span>Created: ${createdDate}</span>`;
|
|
197
|
+
|
|
198
|
+
return `
|
|
199
|
+
<div class="memory-card ${isSelected ? "selected" : ""} ${isPinned ? "pinned" : ""}" data-id="${memory.id}">
|
|
200
|
+
<div class="memory-header">
|
|
201
|
+
<div class="meta">
|
|
202
|
+
<input type="checkbox" class="memory-checkbox" data-id="${memory.id}" ${isSelected ? "checked" : ""} />
|
|
203
|
+
<span class="badge badge-${memory.scope}">${memory.scope}</span>
|
|
204
|
+
${memory.memoryType ? `<span class="badge badge-type">${memory.memoryType}</span>` : ""}
|
|
205
|
+
${isLinked ? '<span class="badge badge-linked">🔗 LINKED</span>' : ""}
|
|
206
|
+
${similarityHtml}
|
|
207
|
+
${isPinned ? '<span class="badge badge-pinned">PINNED</span>' : ""}
|
|
208
|
+
<span class="memory-display-name">${escapeHtml(displayInfo)}</span>
|
|
209
|
+
${subtitle}
|
|
210
|
+
</div>
|
|
211
|
+
<div class="memory-actions">
|
|
212
|
+
${pinButton}
|
|
213
|
+
<button class="btn-edit" onclick="editMemory('${memory.id}')"><i data-lucide="edit-3" class="icon"></i></button>
|
|
214
|
+
<button class="btn-delete" onclick="deleteMemoryWithLink('${memory.id}', ${isLinked})">
|
|
215
|
+
<i data-lucide="trash-2" class="icon"></i>
|
|
216
|
+
${isLinked ? "Delete Pair" : "Delete"}
|
|
217
|
+
</button>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="memory-content markdown-content">${renderMarkdown(memory.content)}</div>
|
|
221
|
+
${isLinked ? '<div class="link-indicator">↑ From prompt below ↓</div>' : ""}
|
|
222
|
+
<div class="memory-footer">
|
|
223
|
+
${dateInfo}
|
|
224
|
+
<span>ID: ${memory.id}</span>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
|
|
176
230
|
function handleCheckboxChange(e) {
|
|
177
231
|
const id = e.target.dataset.id;
|
|
178
232
|
if (e.target.checked) {
|
|
@@ -185,7 +239,9 @@ function handleCheckboxChange(e) {
|
|
|
185
239
|
}
|
|
186
240
|
|
|
187
241
|
function updateCardSelection(id, selected) {
|
|
188
|
-
const card = document.querySelector(
|
|
242
|
+
const card = document.querySelector(
|
|
243
|
+
`.memory-card[data-id="${id}"], .prompt-card[data-id="${id}"]`
|
|
244
|
+
);
|
|
189
245
|
if (card) {
|
|
190
246
|
if (selected) {
|
|
191
247
|
card.classList.add("selected");
|
|
@@ -222,9 +278,10 @@ function updatePagination() {
|
|
|
222
278
|
}
|
|
223
279
|
|
|
224
280
|
function updateSectionTitle() {
|
|
281
|
+
const scopeName = state.currentScope.toUpperCase();
|
|
225
282
|
const title = state.isSearching
|
|
226
283
|
? `└─ SEARCH RESULTS (${state.totalItems}) ──`
|
|
227
|
-
: `└─ MEMORIES (${state.totalItems}) ──`;
|
|
284
|
+
: `└─ ${scopeName} MEMORIES (${state.totalItems}) ──`;
|
|
228
285
|
document.getElementById("section-title").textContent = title;
|
|
229
286
|
}
|
|
230
287
|
|
|
@@ -238,6 +295,20 @@ async function loadStats() {
|
|
|
238
295
|
}
|
|
239
296
|
}
|
|
240
297
|
|
|
298
|
+
function switchScope(scope) {
|
|
299
|
+
state.currentScope = scope;
|
|
300
|
+
state.currentPage = 1;
|
|
301
|
+
state.selectedTag = "";
|
|
302
|
+
|
|
303
|
+
document.querySelectorAll(".tab-btn").forEach((btn) => {
|
|
304
|
+
btn.classList.remove("active");
|
|
305
|
+
});
|
|
306
|
+
document.getElementById(`tab-${scope}`).classList.add("active");
|
|
307
|
+
|
|
308
|
+
populateTagDropdowns();
|
|
309
|
+
loadMemories();
|
|
310
|
+
}
|
|
311
|
+
|
|
241
312
|
async function addMemory(e) {
|
|
242
313
|
e.preventDefault();
|
|
243
314
|
|
|
@@ -266,58 +337,103 @@ async function addMemory(e) {
|
|
|
266
337
|
}
|
|
267
338
|
}
|
|
268
339
|
|
|
269
|
-
async function
|
|
270
|
-
|
|
340
|
+
async function deleteMemoryWithLink(id, isLinked) {
|
|
341
|
+
const message = isLinked ? "Delete this memory AND its linked prompt?" : "Delete this memory?";
|
|
271
342
|
|
|
272
|
-
|
|
343
|
+
if (!confirm(message)) return;
|
|
344
|
+
|
|
345
|
+
const result = await fetchAPI(`/api/memories/${id}?cascade=true`, {
|
|
346
|
+
method: "DELETE",
|
|
347
|
+
});
|
|
273
348
|
|
|
274
349
|
if (result.success) {
|
|
275
|
-
|
|
350
|
+
const msg = result.data?.deletedPrompt ? "Memory and linked prompt deleted" : "Memory deleted";
|
|
351
|
+
showToast(msg, "success");
|
|
352
|
+
|
|
276
353
|
state.selectedMemories.delete(id);
|
|
277
354
|
await loadMemories();
|
|
278
355
|
await loadStats();
|
|
279
|
-
updateBulkActions();
|
|
280
356
|
} else {
|
|
281
|
-
showToast(result.error || "Failed to delete
|
|
357
|
+
showToast(result.error || "Failed to delete", "error");
|
|
282
358
|
}
|
|
283
359
|
}
|
|
284
360
|
|
|
285
|
-
async function
|
|
286
|
-
|
|
361
|
+
async function deletePromptWithLink(id, isLinked) {
|
|
362
|
+
const message = isLinked
|
|
363
|
+
? "Delete this prompt AND its linked memory summary?"
|
|
364
|
+
: "Delete this prompt?";
|
|
287
365
|
|
|
288
|
-
if (!confirm(
|
|
366
|
+
if (!confirm(message)) return;
|
|
289
367
|
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
method: "POST",
|
|
293
|
-
headers: { "Content-Type": "application/json" },
|
|
294
|
-
body: JSON.stringify({ ids }),
|
|
368
|
+
const result = await fetchAPI(`/api/prompts/${id}?cascade=true`, {
|
|
369
|
+
method: "DELETE",
|
|
295
370
|
});
|
|
296
371
|
|
|
297
372
|
if (result.success) {
|
|
298
|
-
|
|
299
|
-
|
|
373
|
+
const msg = result.data?.deletedMemory ? "Prompt and linked memory deleted" : "Prompt deleted";
|
|
374
|
+
showToast(msg, "success");
|
|
375
|
+
|
|
376
|
+
state.selectedMemories.delete(id);
|
|
300
377
|
await loadMemories();
|
|
301
378
|
await loadStats();
|
|
302
|
-
updateBulkActions();
|
|
303
379
|
} else {
|
|
304
|
-
showToast(result.error || "Failed to delete
|
|
380
|
+
showToast(result.error || "Failed to delete", "error");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function bulkDelete() {
|
|
385
|
+
if (state.selectedMemories.size === 0) return;
|
|
386
|
+
|
|
387
|
+
const message = `Delete ${state.selectedMemories.size} selected items (including linked pairs)?`;
|
|
388
|
+
if (!confirm(message)) return;
|
|
389
|
+
|
|
390
|
+
const ids = Array.from(state.selectedMemories);
|
|
391
|
+
|
|
392
|
+
const promptIds = ids.filter((id) => id.startsWith("prompt_"));
|
|
393
|
+
const memoryIds = ids.filter((id) => !id.startsWith("prompt_"));
|
|
394
|
+
|
|
395
|
+
let deletedCount = 0;
|
|
396
|
+
|
|
397
|
+
if (promptIds.length > 0) {
|
|
398
|
+
const result = await fetchAPI("/api/prompts/bulk-delete", {
|
|
399
|
+
method: "POST",
|
|
400
|
+
headers: { "Content-Type": "application/json" },
|
|
401
|
+
body: JSON.stringify({ ids: promptIds, cascade: true }),
|
|
402
|
+
});
|
|
403
|
+
if (result.success) deletedCount += result.data.deleted;
|
|
305
404
|
}
|
|
405
|
+
|
|
406
|
+
if (memoryIds.length > 0) {
|
|
407
|
+
const result = await fetchAPI("/api/memories/bulk-delete", {
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: { "Content-Type": "application/json" },
|
|
410
|
+
body: JSON.stringify({ ids: memoryIds, cascade: true }),
|
|
411
|
+
});
|
|
412
|
+
if (result.success) deletedCount += result.data.deleted;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
showToast(`Deleted ${deletedCount} items (including linked pairs)`, "success");
|
|
416
|
+
state.selectedMemories.clear();
|
|
417
|
+
await loadMemories();
|
|
418
|
+
await loadStats();
|
|
419
|
+
updateBulkActions();
|
|
306
420
|
}
|
|
307
421
|
|
|
308
422
|
function deselectAll() {
|
|
309
423
|
state.selectedMemories.clear();
|
|
310
424
|
document.querySelectorAll(".memory-checkbox").forEach((cb) => (cb.checked = false));
|
|
311
|
-
document
|
|
425
|
+
document
|
|
426
|
+
.querySelectorAll(".memory-card, .prompt-card")
|
|
427
|
+
.forEach((card) => card.classList.remove("selected"));
|
|
312
428
|
updateBulkActions();
|
|
313
429
|
}
|
|
314
430
|
|
|
315
431
|
function editMemory(id) {
|
|
316
|
-
const memory = state.memories.find((m) => m.id === id);
|
|
432
|
+
const memory = state.memories.find((m) => m.id === id && m.type === "memory");
|
|
317
433
|
if (!memory) return;
|
|
318
434
|
|
|
319
435
|
document.getElementById("edit-id").value = memory.id;
|
|
320
|
-
document.getElementById("edit-type").value = memory.
|
|
436
|
+
document.getElementById("edit-type").value = memory.memoryType || "";
|
|
321
437
|
document.getElementById("edit-content").value = memory.content;
|
|
322
438
|
|
|
323
439
|
document.getElementById("edit-modal").classList.remove("hidden");
|
|
@@ -390,34 +506,6 @@ function changePage(delta) {
|
|
|
390
506
|
loadMemories();
|
|
391
507
|
}
|
|
392
508
|
|
|
393
|
-
function handleFilterChange() {
|
|
394
|
-
const scopeFilter = document.getElementById("scope-filter").value;
|
|
395
|
-
const tagFilter = document.getElementById("tag-filter").value;
|
|
396
|
-
|
|
397
|
-
if (scopeFilter) {
|
|
398
|
-
const filteredTags = scopeFilter === "user" ? state.tags.user : state.tags.project;
|
|
399
|
-
state.selectedTag = filteredTags.length > 0 ? filteredTags[0].tag : "";
|
|
400
|
-
|
|
401
|
-
const tagDropdown = document.getElementById("tag-filter");
|
|
402
|
-
tagDropdown.innerHTML = '<option value="">All Tags</option>';
|
|
403
|
-
filteredTags.forEach((tagInfo) => {
|
|
404
|
-
const displayText = tagInfo.displayName || tagInfo.tag;
|
|
405
|
-
const shortDisplay =
|
|
406
|
-
displayText.length > 50 ? displayText.substring(0, 50) + "..." : displayText;
|
|
407
|
-
const option = document.createElement("option");
|
|
408
|
-
option.value = tagInfo.tag;
|
|
409
|
-
option.textContent = `[${scopeFilter}] ${shortDisplay}`;
|
|
410
|
-
if (tagInfo.tag === state.selectedTag) option.selected = true;
|
|
411
|
-
tagDropdown.appendChild(option);
|
|
412
|
-
});
|
|
413
|
-
} else {
|
|
414
|
-
state.selectedTag = tagFilter;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
state.currentPage = 1;
|
|
418
|
-
loadMemories();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
509
|
function handleAddScopeChange() {
|
|
422
510
|
const scope = document.getElementById("add-scope").value;
|
|
423
511
|
const tagDropdown = document.getElementById("add-tag");
|
|
@@ -598,7 +686,7 @@ async function runMigration(strategy) {
|
|
|
598
686
|
|
|
599
687
|
if (
|
|
600
688
|
!confirm(
|
|
601
|
-
`Run ${strategyName} migration?\n\nThis operation is IRREVERSIBLE and will:\n${strategy === "fresh-start" ? "- DELETE all existing memories\n- Remove all shards" : "- Re-embed all memories with new model\n- This may take
|
|
689
|
+
`Run ${strategyName} migration?\n\nThis operation is IRREVERSIBLE and will:\n${strategy === "fresh-start" ? "- DELETE all existing memories\n- Remove all shards" : "- Re-embed all memories with new model\n- This may take several minutes"}\n\nContinue?`
|
|
602
690
|
)
|
|
603
691
|
) {
|
|
604
692
|
return;
|
|
@@ -619,7 +707,7 @@ async function runMigration(strategy) {
|
|
|
619
707
|
if (strategy === "fresh-start") {
|
|
620
708
|
message += `Deleted ${data.deletedShards} shard(s). Duration: ${(data.duration / 1000).toFixed(2)}s`;
|
|
621
709
|
} else {
|
|
622
|
-
message += `Re-embedded ${data.
|
|
710
|
+
message += `Re-embedded ${data.reEmbeddedMemories} memories. Duration: ${(data.duration / 1000).toFixed(2)}s`;
|
|
623
711
|
}
|
|
624
712
|
|
|
625
713
|
showToast(message, "success");
|
|
@@ -635,7 +723,9 @@ async function runMigration(strategy) {
|
|
|
635
723
|
}
|
|
636
724
|
|
|
637
725
|
document.addEventListener("DOMContentLoaded", async () => {
|
|
638
|
-
document.getElementById("
|
|
726
|
+
document.getElementById("tab-project").addEventListener("click", () => switchScope("project"));
|
|
727
|
+
document.getElementById("tab-user").addEventListener("click", () => switchScope("user"));
|
|
728
|
+
|
|
639
729
|
document.getElementById("tag-filter").addEventListener("change", () => {
|
|
640
730
|
state.selectedTag = document.getElementById("tag-filter").value;
|
|
641
731
|
state.currentPage = 1;
|
package/dist/web/index.html
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
<link rel="stylesheet" href="/styles.css" />
|
|
8
8
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
9
9
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/marked@17.0.1/lib/marked.umd.min.js"></script>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.2/dist/purify.min.js"></script>
|
|
10
12
|
</head>
|
|
11
13
|
<body>
|
|
12
14
|
<div class="container">
|
|
@@ -33,16 +35,18 @@
|
|
|
33
35
|
</div>
|
|
34
36
|
</header>
|
|
35
37
|
|
|
36
|
-
<div class="
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</
|
|
38
|
+
<div class="scope-tabs">
|
|
39
|
+
<button id="tab-project" class="tab-btn active">
|
|
40
|
+
<i data-lucide="folder" class="icon"></i>
|
|
41
|
+
PROJECT TIMELINE
|
|
42
|
+
</button>
|
|
43
|
+
<button id="tab-user" class="tab-btn">
|
|
44
|
+
<i data-lucide="user" class="icon"></i>
|
|
45
|
+
USER TIMELINE
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
45
48
|
|
|
49
|
+
<div class="controls">
|
|
46
50
|
<div class="filter-group">
|
|
47
51
|
<label>Tag:</label>
|
|
48
52
|
<select id="tag-filter">
|
|
@@ -97,7 +101,7 @@
|
|
|
97
101
|
|
|
98
102
|
<div class="memories-section">
|
|
99
103
|
<div class="section-header">
|
|
100
|
-
<h2 id="section-title">└─ MEMORIES (0) ──</h2>
|
|
104
|
+
<h2 id="section-title">└─ PROJECT MEMORIES (0) ──</h2>
|
|
101
105
|
<div class="pagination" id="pagination-top">
|
|
102
106
|
<button id="prev-page-top" disabled>
|
|
103
107
|
<i data-lucide="chevron-left" class="icon"></i>
|