total-recall-brain 3.1.2 → 3.2.1
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/extension/background.js +99 -0
- package/extension/content-script.css +16 -0
- package/extension/content-script.js +134 -0
- package/extension/icons/README.md +27 -0
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/lib/brain-client.js +47 -0
- package/extension/manifest.json +39 -0
- package/extension/options/options.css +218 -0
- package/extension/options/options.html +72 -0
- package/extension/options/options.js +99 -0
- package/extension/popup/popup.css +221 -0
- package/extension/popup/popup.html +44 -0
- package/extension/popup/popup.js +107 -0
- package/extension/sidepanel/sidepanel.css +340 -0
- package/extension/sidepanel/sidepanel.html +38 -0
- package/extension/sidepanel/sidepanel.js +229 -0
- package/frontend/README.md +73 -0
- package/frontend/index.html +17 -0
- package/frontend/package.json +31 -0
- package/frontend/src/App.css +2 -0
- package/frontend/src/App.tsx +390 -0
- package/frontend/src/api.ts +688 -0
- package/frontend/src/assets/hero.png +0 -0
- package/frontend/src/assets/react.svg +1 -0
- package/frontend/src/assets/vite.svg +1 -0
- package/frontend/src/components/BrainSelector.tsx +272 -0
- package/frontend/src/components/DaemonLogsTab.tsx +54 -0
- package/frontend/src/components/Graph3D.tsx +850 -0
- package/frontend/src/components/MarkdownUtils.tsx +99 -0
- package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
- package/frontend/src/components/TaskQueueTab.tsx +185 -0
- package/frontend/src/index.css +1589 -0
- package/frontend/src/main.tsx +10 -0
- package/frontend/src/pages/ApiKeysPage.tsx +288 -0
- package/frontend/src/pages/AutomationsPage.tsx +354 -0
- package/frontend/src/pages/ChatPage.tsx +806 -0
- package/frontend/src/pages/DeploymentsPage.tsx +275 -0
- package/frontend/src/pages/FilesPage.tsx +384 -0
- package/frontend/src/pages/HealthPage.tsx +91 -0
- package/frontend/src/pages/IntegrationsPage.tsx +382 -0
- package/frontend/src/pages/LoginPage.tsx +237 -0
- package/frontend/src/pages/MemoryPage.tsx +1192 -0
- package/frontend/src/pages/SandboxPage.tsx +113 -0
- package/frontend/src/pages/SettingsPage.tsx +662 -0
- package/frontend/src/pages/SkillsPage.tsx +618 -0
- package/frontend/src/pages/TasksPage.tsx +392 -0
- package/frontend/src/pages/UsagePage.tsx +260 -0
- package/frontend/src/types.ts +163 -0
- package/frontend/tsconfig.app.json +25 -0
- package/frontend/tsconfig.json +7 -0
- package/frontend/tsconfig.node.json +24 -0
- package/frontend/vite.config.ts +24 -0
- package/package.json +8 -1
- package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/* Total Recall — Side Panel Styles (Catppuccin Mocha) */
|
|
2
|
+
|
|
3
|
+
* {
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
background: #11111b;
|
|
12
|
+
color: #cdd6f4;
|
|
13
|
+
width: 100%;
|
|
14
|
+
min-height: 100vh;
|
|
15
|
+
overflow-x: hidden;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.panel {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
height: 100vh;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* ---- Header ---- */
|
|
25
|
+
.panel-header {
|
|
26
|
+
position: sticky;
|
|
27
|
+
top: 0;
|
|
28
|
+
z-index: 10;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: space-between;
|
|
32
|
+
padding: 14px 16px;
|
|
33
|
+
background: #181825;
|
|
34
|
+
border-bottom: 1px solid #313244;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.panel-header h1 {
|
|
38
|
+
font-size: 16px;
|
|
39
|
+
font-weight: 700;
|
|
40
|
+
letter-spacing: 0.3px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.dot {
|
|
44
|
+
width: 10px;
|
|
45
|
+
height: 10px;
|
|
46
|
+
border-radius: 50%;
|
|
47
|
+
background: #6c7086;
|
|
48
|
+
transition: background 0.3s ease;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.dot.connected {
|
|
52
|
+
background: #a6e3a1;
|
|
53
|
+
box-shadow: 0 0 6px #a6e3a1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.dot.disconnected {
|
|
57
|
+
background: #f38ba8;
|
|
58
|
+
box-shadow: 0 0 6px #f38ba8;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ---- Search ---- */
|
|
62
|
+
.search-box {
|
|
63
|
+
padding: 12px 16px 8px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.search-box input {
|
|
67
|
+
width: 100%;
|
|
68
|
+
padding: 10px 14px;
|
|
69
|
+
background: #1e1e2e;
|
|
70
|
+
border: 1px solid #313244;
|
|
71
|
+
border-radius: 10px;
|
|
72
|
+
color: #cdd6f4;
|
|
73
|
+
font-size: 14px;
|
|
74
|
+
outline: none;
|
|
75
|
+
transition: border-color 0.2s ease;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.search-box input::placeholder {
|
|
79
|
+
color: #6c7086;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.search-box input:focus {
|
|
83
|
+
border-color: #89b4fa;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ---- Memories List ---- */
|
|
87
|
+
.memories-list {
|
|
88
|
+
flex: 1;
|
|
89
|
+
overflow-y: auto;
|
|
90
|
+
padding: 8px 16px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.memory-card {
|
|
94
|
+
background: #1e1e2e;
|
|
95
|
+
border-radius: 12px;
|
|
96
|
+
padding: 12px 14px;
|
|
97
|
+
margin-bottom: 10px;
|
|
98
|
+
border: 1px solid #313244;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
transition: border-color 0.2s ease, transform 0.15s ease;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.memory-card:hover {
|
|
104
|
+
border-color: #89b4fa;
|
|
105
|
+
transform: translateY(-1px);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.memory-card .card-header {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: space-between;
|
|
112
|
+
margin-bottom: 6px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.memory-card .card-title {
|
|
116
|
+
font-size: 13px;
|
|
117
|
+
font-weight: 600;
|
|
118
|
+
color: #cdd6f4;
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
text-overflow: ellipsis;
|
|
121
|
+
white-space: nowrap;
|
|
122
|
+
flex: 1;
|
|
123
|
+
margin-right: 8px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.memory-card .card-badge {
|
|
127
|
+
font-size: 10px;
|
|
128
|
+
font-weight: 600;
|
|
129
|
+
padding: 2px 8px;
|
|
130
|
+
border-radius: 999px;
|
|
131
|
+
background: #313244;
|
|
132
|
+
color: #89b4fa;
|
|
133
|
+
text-transform: uppercase;
|
|
134
|
+
letter-spacing: 0.5px;
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.memory-card .card-excerpt {
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
color: #a6adc8;
|
|
141
|
+
line-height: 1.5;
|
|
142
|
+
display: -webkit-box;
|
|
143
|
+
-webkit-line-clamp: 2;
|
|
144
|
+
-webkit-box-orient: vertical;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.memory-card .card-time {
|
|
149
|
+
font-size: 10px;
|
|
150
|
+
color: #585b70;
|
|
151
|
+
margin-top: 6px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.memories-empty {
|
|
155
|
+
text-align: center;
|
|
156
|
+
color: #585b70;
|
|
157
|
+
font-size: 13px;
|
|
158
|
+
padding: 24px 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* ---- Quick Actions ---- */
|
|
162
|
+
.quick-actions {
|
|
163
|
+
display: flex;
|
|
164
|
+
gap: 8px;
|
|
165
|
+
padding: 12px 16px;
|
|
166
|
+
border-top: 1px solid #313244;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.action-btn {
|
|
170
|
+
flex: 1;
|
|
171
|
+
padding: 8px 6px;
|
|
172
|
+
font-size: 12px;
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
background: #1e1e2e;
|
|
175
|
+
color: #cdd6f4;
|
|
176
|
+
border: 1px solid #313244;
|
|
177
|
+
border-radius: 10px;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
transition: background 0.2s ease, border-color 0.2s ease;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.action-btn:hover {
|
|
183
|
+
background: #313244;
|
|
184
|
+
border-color: #89b4fa;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.action-btn.accent {
|
|
188
|
+
background: #89b4fa;
|
|
189
|
+
color: #11111b;
|
|
190
|
+
border-color: #89b4fa;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.action-btn.accent:hover {
|
|
194
|
+
background: #74c7ec;
|
|
195
|
+
border-color: #74c7ec;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* ---- Note Area ---- */
|
|
199
|
+
.note-area {
|
|
200
|
+
padding: 0 16px 12px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.note-area textarea {
|
|
204
|
+
width: 100%;
|
|
205
|
+
min-height: 80px;
|
|
206
|
+
padding: 10px 12px;
|
|
207
|
+
background: #1e1e2e;
|
|
208
|
+
border: 1px solid #313244;
|
|
209
|
+
border-radius: 10px;
|
|
210
|
+
color: #cdd6f4;
|
|
211
|
+
font-size: 13px;
|
|
212
|
+
resize: vertical;
|
|
213
|
+
outline: none;
|
|
214
|
+
font-family: inherit;
|
|
215
|
+
transition: border-color 0.2s ease;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.note-area textarea:focus {
|
|
219
|
+
border-color: #89b4fa;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.note-actions {
|
|
223
|
+
display: flex;
|
|
224
|
+
gap: 8px;
|
|
225
|
+
margin-top: 8px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.hidden {
|
|
229
|
+
display: none !important;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* ---- Research Feed ---- */
|
|
233
|
+
.research-feed {
|
|
234
|
+
padding: 12px 16px 16px;
|
|
235
|
+
border-top: 1px solid #313244;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.research-feed h3 {
|
|
239
|
+
font-size: 12px;
|
|
240
|
+
font-weight: 700;
|
|
241
|
+
text-transform: uppercase;
|
|
242
|
+
letter-spacing: 0.8px;
|
|
243
|
+
color: #585b70;
|
|
244
|
+
margin-bottom: 10px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.research-item {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 8px;
|
|
251
|
+
padding: 8px 10px;
|
|
252
|
+
background: #1e1e2e;
|
|
253
|
+
border-radius: 10px;
|
|
254
|
+
margin-bottom: 6px;
|
|
255
|
+
border: 1px solid #313244;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.research-item .spinner {
|
|
259
|
+
width: 12px;
|
|
260
|
+
height: 12px;
|
|
261
|
+
border: 2px solid #313244;
|
|
262
|
+
border-top-color: #89b4fa;
|
|
263
|
+
border-radius: 50%;
|
|
264
|
+
animation: spin 0.8s linear infinite;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@keyframes spin {
|
|
268
|
+
to { transform: rotate(360deg); }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.research-item .topic {
|
|
272
|
+
font-size: 12px;
|
|
273
|
+
color: #a6adc8;
|
|
274
|
+
flex: 1;
|
|
275
|
+
overflow: hidden;
|
|
276
|
+
text-overflow: ellipsis;
|
|
277
|
+
white-space: nowrap;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.research-item .status-badge {
|
|
281
|
+
font-size: 10px;
|
|
282
|
+
padding: 2px 6px;
|
|
283
|
+
border-radius: 999px;
|
|
284
|
+
background: rgba(137, 180, 250, 0.15);
|
|
285
|
+
color: #89b4fa;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.research-empty {
|
|
289
|
+
font-size: 12px;
|
|
290
|
+
color: #585b70;
|
|
291
|
+
text-align: center;
|
|
292
|
+
padding: 8px 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* ---- Toast Notification ---- */
|
|
296
|
+
.toast {
|
|
297
|
+
position: fixed;
|
|
298
|
+
bottom: 16px;
|
|
299
|
+
left: 50%;
|
|
300
|
+
transform: translateX(-50%) translateY(60px);
|
|
301
|
+
padding: 10px 20px;
|
|
302
|
+
background: #1e1e2e;
|
|
303
|
+
color: #a6e3a1;
|
|
304
|
+
border: 1px solid #a6e3a1;
|
|
305
|
+
border-radius: 10px;
|
|
306
|
+
font-size: 13px;
|
|
307
|
+
font-weight: 600;
|
|
308
|
+
opacity: 0;
|
|
309
|
+
transition: transform 0.3s ease, opacity 0.3s ease;
|
|
310
|
+
z-index: 100;
|
|
311
|
+
pointer-events: none;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.toast.show {
|
|
315
|
+
transform: translateX(-50%) translateY(0);
|
|
316
|
+
opacity: 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.toast.error {
|
|
320
|
+
color: #f38ba8;
|
|
321
|
+
border-color: #f38ba8;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* ---- Scrollbar ---- */
|
|
325
|
+
::-webkit-scrollbar {
|
|
326
|
+
width: 6px;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
::-webkit-scrollbar-track {
|
|
330
|
+
background: transparent;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
::-webkit-scrollbar-thumb {
|
|
334
|
+
background: #313244;
|
|
335
|
+
border-radius: 3px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
::-webkit-scrollbar-thumb:hover {
|
|
339
|
+
background: #45475a;
|
|
340
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Total Recall</title>
|
|
6
|
+
<link rel="stylesheet" href="sidepanel.css">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div class="panel">
|
|
10
|
+
<header class="panel-header">
|
|
11
|
+
<h1>🧠 Total Recall</h1>
|
|
12
|
+
<div id="connection-dot" class="dot"></div>
|
|
13
|
+
</header>
|
|
14
|
+
<div class="search-box">
|
|
15
|
+
<input type="text" id="search-input" placeholder="Search your brain..." />
|
|
16
|
+
</div>
|
|
17
|
+
<div id="memories-list" class="memories-list"></div>
|
|
18
|
+
<div class="quick-actions">
|
|
19
|
+
<button id="btn-remember" class="action-btn">📌 Remember Page</button>
|
|
20
|
+
<button id="btn-research" class="action-btn">🔬 Research Page</button>
|
|
21
|
+
<button id="btn-note" class="action-btn">📝 Quick Note</button>
|
|
22
|
+
</div>
|
|
23
|
+
<div id="note-area" class="note-area hidden">
|
|
24
|
+
<textarea id="note-input" placeholder="Type your note..."></textarea>
|
|
25
|
+
<div class="note-actions">
|
|
26
|
+
<button id="btn-save-note" class="action-btn accent">Save Note</button>
|
|
27
|
+
<button id="btn-cancel-note" class="action-btn">Cancel</button>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div id="research-feed" class="research-feed">
|
|
31
|
+
<h3>Active Research</h3>
|
|
32
|
+
<div id="research-list"></div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<script src="../lib/brain-client.js"></script>
|
|
36
|
+
<script src="sidepanel.js"></script>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// Total Recall — Side Panel Logic
|
|
2
|
+
|
|
3
|
+
(function () {
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
// ---- DOM refs ----
|
|
7
|
+
const dot = document.getElementById('connection-dot');
|
|
8
|
+
const searchInput = document.getElementById('search-input');
|
|
9
|
+
const memoriesList = document.getElementById('memories-list');
|
|
10
|
+
const btnRemember = document.getElementById('btn-remember');
|
|
11
|
+
const btnResearch = document.getElementById('btn-research');
|
|
12
|
+
const btnNote = document.getElementById('btn-note');
|
|
13
|
+
const noteArea = document.getElementById('note-area');
|
|
14
|
+
const noteInput = document.getElementById('note-input');
|
|
15
|
+
const btnSaveNote = document.getElementById('btn-save-note');
|
|
16
|
+
const btnCancelNote = document.getElementById('btn-cancel-note');
|
|
17
|
+
const researchList = document.getElementById('research-list');
|
|
18
|
+
|
|
19
|
+
// ---- Connection check ----
|
|
20
|
+
async function checkConnection() {
|
|
21
|
+
try {
|
|
22
|
+
await self.BrainClient.healthCheck();
|
|
23
|
+
dot.className = 'dot connected';
|
|
24
|
+
} catch {
|
|
25
|
+
dot.className = 'dot disconnected';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---- Debounced search ----
|
|
30
|
+
let searchTimer = null;
|
|
31
|
+
|
|
32
|
+
function debounceSearch(query) {
|
|
33
|
+
clearTimeout(searchTimer);
|
|
34
|
+
if (!query.trim()) {
|
|
35
|
+
memoriesList.innerHTML = '<div class="memories-empty">Search your brain for related memories</div>';
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
searchTimer = setTimeout(() => {
|
|
39
|
+
performSearch(query.trim());
|
|
40
|
+
}, 300);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function performSearch(query) {
|
|
44
|
+
memoriesList.innerHTML = '<div class="memories-empty">Searching…</div>';
|
|
45
|
+
try {
|
|
46
|
+
const response = await new Promise((resolve, reject) => {
|
|
47
|
+
chrome.runtime.sendMessage({ type: 'QUERY_BRAIN', query, topK: 10 }, (res) => {
|
|
48
|
+
if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
|
|
49
|
+
resolve(res);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
renderMemories(response.memories || []);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
memoriesList.innerHTML = `<div class="memories-empty">Error: ${err.message}</div>`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---- Render memories ----
|
|
59
|
+
function renderMemories(memories) {
|
|
60
|
+
if (!memories.length) {
|
|
61
|
+
memoriesList.innerHTML = '<div class="memories-empty">No memories found</div>';
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
memoriesList.innerHTML = memories.map((m) => {
|
|
65
|
+
const title = m.title || m.slug || 'Untitled';
|
|
66
|
+
const excerpt = m.content || m.excerpt || '';
|
|
67
|
+
const category = m.category || 'memory';
|
|
68
|
+
const time = m.created_at ? formatTime(m.created_at) : '';
|
|
69
|
+
return `
|
|
70
|
+
<div class="memory-card">
|
|
71
|
+
<div class="card-header">
|
|
72
|
+
<span class="card-title">${escapeHtml(title)}</span>
|
|
73
|
+
<span class="card-badge">${escapeHtml(category)}</span>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="card-excerpt">${escapeHtml(excerpt)}</div>
|
|
76
|
+
${time ? `<div class="card-time">${time}</div>` : ''}
|
|
77
|
+
</div>
|
|
78
|
+
`;
|
|
79
|
+
}).join('');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- Quick actions ----
|
|
83
|
+
async function getCurrentTab() {
|
|
84
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
85
|
+
return tab;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
btnRemember.addEventListener('click', async () => {
|
|
89
|
+
try {
|
|
90
|
+
const tab = await getCurrentTab();
|
|
91
|
+
await sendShare({
|
|
92
|
+
url: tab.url,
|
|
93
|
+
title: tab.title,
|
|
94
|
+
action: 'remember',
|
|
95
|
+
source: 'chrome-extension-sidepanel'
|
|
96
|
+
});
|
|
97
|
+
showToast('Page remembered!');
|
|
98
|
+
} catch (err) {
|
|
99
|
+
showToast('Error: ' + err.message, true);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
btnResearch.addEventListener('click', async () => {
|
|
104
|
+
try {
|
|
105
|
+
const tab = await getCurrentTab();
|
|
106
|
+
await sendShare({
|
|
107
|
+
url: tab.url,
|
|
108
|
+
title: tab.title,
|
|
109
|
+
action: 'research',
|
|
110
|
+
source: 'chrome-extension-sidepanel'
|
|
111
|
+
});
|
|
112
|
+
showToast('Research queued!');
|
|
113
|
+
} catch (err) {
|
|
114
|
+
showToast('Error: ' + err.message, true);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ---- Quick Note ----
|
|
119
|
+
btnNote.addEventListener('click', () => {
|
|
120
|
+
noteArea.classList.toggle('hidden');
|
|
121
|
+
if (!noteArea.classList.contains('hidden')) {
|
|
122
|
+
noteInput.focus();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
btnCancelNote.addEventListener('click', () => {
|
|
127
|
+
noteArea.classList.add('hidden');
|
|
128
|
+
noteInput.value = '';
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
btnSaveNote.addEventListener('click', async () => {
|
|
132
|
+
const text = noteInput.value.trim();
|
|
133
|
+
if (!text) return;
|
|
134
|
+
try {
|
|
135
|
+
const tab = await getCurrentTab();
|
|
136
|
+
await sendShare({
|
|
137
|
+
url: tab.url,
|
|
138
|
+
title: `Note: ${text.slice(0, 50)}`,
|
|
139
|
+
excerpt: text,
|
|
140
|
+
action: 'remember',
|
|
141
|
+
category: 'fact',
|
|
142
|
+
source: 'chrome-extension-sidepanel'
|
|
143
|
+
});
|
|
144
|
+
noteInput.value = '';
|
|
145
|
+
noteArea.classList.add('hidden');
|
|
146
|
+
showToast('Note saved!');
|
|
147
|
+
} catch (err) {
|
|
148
|
+
showToast('Error: ' + err.message, true);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ---- Share helper ----
|
|
153
|
+
function sendShare(data) {
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
chrome.runtime.sendMessage({ type: 'SHARE', data }, (res) => {
|
|
156
|
+
if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
|
|
157
|
+
if (res && res.success) return resolve(res);
|
|
158
|
+
reject(new Error(res?.error || 'Unknown error'));
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ---- Research feed ----
|
|
164
|
+
async function fetchResearch() {
|
|
165
|
+
try {
|
|
166
|
+
const data = await self.BrainClient.brainFetch('/api/research?status=in_progress');
|
|
167
|
+
const projects = data.projects || data.results || [];
|
|
168
|
+
if (!projects.length) {
|
|
169
|
+
researchList.innerHTML = '<div class="research-empty">No active research</div>';
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
researchList.innerHTML = projects.map((p) => `
|
|
173
|
+
<div class="research-item">
|
|
174
|
+
<div class="spinner"></div>
|
|
175
|
+
<span class="topic">${escapeHtml(p.topic || p.title || 'Untitled')}</span>
|
|
176
|
+
<span class="status-badge">${escapeHtml(p.status || 'running')}</span>
|
|
177
|
+
</div>
|
|
178
|
+
`).join('');
|
|
179
|
+
} catch {
|
|
180
|
+
researchList.innerHTML = '<div class="research-empty">Could not load research</div>';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---- Toast ----
|
|
185
|
+
function showToast(message, isError = false) {
|
|
186
|
+
let toast = document.querySelector('.toast');
|
|
187
|
+
if (!toast) {
|
|
188
|
+
toast = document.createElement('div');
|
|
189
|
+
toast.className = 'toast';
|
|
190
|
+
document.body.appendChild(toast);
|
|
191
|
+
}
|
|
192
|
+
toast.textContent = message;
|
|
193
|
+
toast.classList.toggle('error', isError);
|
|
194
|
+
// Force reflow to restart animation
|
|
195
|
+
toast.classList.remove('show');
|
|
196
|
+
void toast.offsetWidth;
|
|
197
|
+
toast.classList.add('show');
|
|
198
|
+
setTimeout(() => toast.classList.remove('show'), 2500);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---- Helpers ----
|
|
202
|
+
function escapeHtml(str) {
|
|
203
|
+
const div = document.createElement('div');
|
|
204
|
+
div.textContent = str;
|
|
205
|
+
return div.innerHTML;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function formatTime(dateStr) {
|
|
209
|
+
try {
|
|
210
|
+
const d = new Date(dateStr);
|
|
211
|
+
const now = new Date();
|
|
212
|
+
const diff = now - d;
|
|
213
|
+
if (diff < 60000) return 'just now';
|
|
214
|
+
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
|
|
215
|
+
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
|
|
216
|
+
return d.toLocaleDateString();
|
|
217
|
+
} catch {
|
|
218
|
+
return '';
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---- Event listeners ----
|
|
223
|
+
searchInput.addEventListener('input', (e) => debounceSearch(e.target.value));
|
|
224
|
+
|
|
225
|
+
// ---- Init ----
|
|
226
|
+
checkConnection();
|
|
227
|
+
fetchResearch();
|
|
228
|
+
setInterval(fetchResearch, 30000);
|
|
229
|
+
})();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="description" content="Total Recall — Sovereign AI Brain Dashboard. Memory explorer, chat interface, code sandbox, and system health monitoring." />
|
|
7
|
+
<meta name="theme-color" content="#0a0a0f" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
11
|
+
<title>Total Recall — Sovereign Brain</title>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|