total-recall-brain 3.1.1 → 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
- package/src/cli/remember.mjs +1 -1
- package/src/core/research-queue.mjs +5 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Total Recall — Background Service Worker
|
|
2
|
+
// Handles context menus, message routing, and passive browsing buffer.
|
|
3
|
+
|
|
4
|
+
importScripts('lib/brain-client.js');
|
|
5
|
+
|
|
6
|
+
// Destructure for convenience after importScripts populates self.BrainClient
|
|
7
|
+
const { share, search, healthCheck } = self.BrainClient;
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Context menu registration
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
13
|
+
chrome.contextMenus.create({
|
|
14
|
+
id: 'send-to-brain',
|
|
15
|
+
title: 'Send to Brain',
|
|
16
|
+
contexts: ['page']
|
|
17
|
+
});
|
|
18
|
+
chrome.contextMenus.create({
|
|
19
|
+
id: 'remember-this',
|
|
20
|
+
title: 'Remember This',
|
|
21
|
+
contexts: ['selection']
|
|
22
|
+
});
|
|
23
|
+
chrome.contextMenus.create({
|
|
24
|
+
id: 'research-this',
|
|
25
|
+
title: 'Research This',
|
|
26
|
+
contexts: ['selection']
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Context menu click handler
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
|
34
|
+
try {
|
|
35
|
+
if (info.menuItemId === 'send-to-brain') {
|
|
36
|
+
await share({
|
|
37
|
+
url: tab.url,
|
|
38
|
+
title: tab.title,
|
|
39
|
+
action: 'research',
|
|
40
|
+
source: 'chrome-extension'
|
|
41
|
+
});
|
|
42
|
+
showBadge('\u2713', '#22c55e');
|
|
43
|
+
} else if (info.menuItemId === 'remember-this') {
|
|
44
|
+
await share({
|
|
45
|
+
url: tab.url,
|
|
46
|
+
title: tab.title,
|
|
47
|
+
excerpt: info.selectionText,
|
|
48
|
+
action: 'remember',
|
|
49
|
+
source: 'chrome-extension'
|
|
50
|
+
});
|
|
51
|
+
showBadge('\u2713', '#22c55e');
|
|
52
|
+
} else if (info.menuItemId === 'research-this') {
|
|
53
|
+
await share({
|
|
54
|
+
excerpt: info.selectionText,
|
|
55
|
+
action: 'research',
|
|
56
|
+
source: 'chrome-extension'
|
|
57
|
+
});
|
|
58
|
+
showBadge('\u2713', '#22c55e');
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error('Brain API error:', err);
|
|
62
|
+
showBadge('!', '#ef4444');
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Badge helper
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
function showBadge(text, color) {
|
|
70
|
+
chrome.action.setBadgeText({ text });
|
|
71
|
+
chrome.action.setBadgeBackgroundColor({ color });
|
|
72
|
+
setTimeout(() => chrome.action.setBadgeText({ text: '' }), 3000);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Message listener (popup, content-script, sidepanel → background)
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
79
|
+
if (msg.type === 'QUERY_BRAIN') {
|
|
80
|
+
search(msg.query, msg.topK || 3)
|
|
81
|
+
.then(data => sendResponse({ memories: data.results || [] }))
|
|
82
|
+
.catch(err => sendResponse({ memories: [], error: err.message }));
|
|
83
|
+
return true; // keep channel open for async response
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (msg.type === 'SHARE') {
|
|
87
|
+
share(msg.data)
|
|
88
|
+
.then(data => sendResponse({ success: true, ...data }))
|
|
89
|
+
.catch(err => sendResponse({ success: false, error: err.message }));
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (msg.type === 'HEALTH_CHECK') {
|
|
94
|
+
healthCheck()
|
|
95
|
+
.then(data => sendResponse({ connected: true, ...data }))
|
|
96
|
+
.catch(() => sendResponse({ connected: false }));
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* Total Recall — Content Script Styles
|
|
2
|
+
* The floating pill uses Shadow DOM for style isolation,
|
|
3
|
+
* so this file only contains the host-level positioning.
|
|
4
|
+
* Keeping it minimal to avoid conflicts with host pages. */
|
|
5
|
+
|
|
6
|
+
#total-recall-pill-host {
|
|
7
|
+
/* Position is handled inline via Shadow DOM :host styles.
|
|
8
|
+
* This rule serves as a fallback and ensures the element
|
|
9
|
+
* doesn't inherit unintended styles from the host page. */
|
|
10
|
+
all: initial;
|
|
11
|
+
position: fixed !important;
|
|
12
|
+
bottom: 20px !important;
|
|
13
|
+
right: 20px !important;
|
|
14
|
+
z-index: 2147483647 !important;
|
|
15
|
+
pointer-events: auto !important;
|
|
16
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Total Recall — Content Script
|
|
2
|
+
// Injected into every page. Queries the brain for related memories and shows
|
|
3
|
+
// a floating pill in the bottom-right corner when matches are found.
|
|
4
|
+
// Uses Shadow DOM to isolate styles from the host page.
|
|
5
|
+
|
|
6
|
+
(function () {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// ---- Capture page context ----
|
|
10
|
+
const pageContext = {
|
|
11
|
+
url: location.href,
|
|
12
|
+
title: document.title,
|
|
13
|
+
description: document.querySelector('meta[name="description"]')?.content || ''
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// ---- Skip non-content pages ----
|
|
17
|
+
if (pageContext.url.startsWith('chrome://') ||
|
|
18
|
+
pageContext.url.startsWith('chrome-extension://') ||
|
|
19
|
+
pageContext.url.startsWith('about:')) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let pillHost = null;
|
|
24
|
+
|
|
25
|
+
// ---- Create floating pill with Shadow DOM ----
|
|
26
|
+
function createPill(count) {
|
|
27
|
+
if (pillHost) pillHost.remove();
|
|
28
|
+
|
|
29
|
+
pillHost = document.createElement('div');
|
|
30
|
+
pillHost.id = 'total-recall-pill-host';
|
|
31
|
+
|
|
32
|
+
const shadow = pillHost.attachShadow({ mode: 'closed' });
|
|
33
|
+
|
|
34
|
+
const style = document.createElement('style');
|
|
35
|
+
style.textContent = `
|
|
36
|
+
:host {
|
|
37
|
+
all: initial;
|
|
38
|
+
position: fixed;
|
|
39
|
+
bottom: 20px;
|
|
40
|
+
right: 20px;
|
|
41
|
+
z-index: 2147483647;
|
|
42
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.pill {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 6px;
|
|
49
|
+
padding: 8px 14px;
|
|
50
|
+
background: #1e1e2e;
|
|
51
|
+
color: #cdd6f4;
|
|
52
|
+
border: 1px solid #313244;
|
|
53
|
+
border-radius: 999px;
|
|
54
|
+
font-size: 13px;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
|
|
58
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
|
59
|
+
user-select: none;
|
|
60
|
+
opacity: 0;
|
|
61
|
+
transform: translateY(10px);
|
|
62
|
+
animation: pill-enter 0.3s ease forwards;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.pill:hover {
|
|
66
|
+
border-color: #89b4fa;
|
|
67
|
+
transform: translateY(-2px);
|
|
68
|
+
box-shadow: 0 6px 24px rgba(137, 180, 250, 0.15);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.pill .brain-icon {
|
|
72
|
+
font-size: 15px;
|
|
73
|
+
line-height: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.pill .count {
|
|
77
|
+
display: inline-flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
min-width: 18px;
|
|
81
|
+
height: 18px;
|
|
82
|
+
padding: 0 5px;
|
|
83
|
+
background: #89b4fa;
|
|
84
|
+
color: #11111b;
|
|
85
|
+
border-radius: 999px;
|
|
86
|
+
font-size: 11px;
|
|
87
|
+
font-weight: 700;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@keyframes pill-enter {
|
|
91
|
+
to {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
transform: translateY(0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const pill = document.createElement('div');
|
|
99
|
+
pill.className = 'pill';
|
|
100
|
+
pill.innerHTML = `
|
|
101
|
+
<span class="brain-icon">🧠</span>
|
|
102
|
+
<span>${count} related</span>
|
|
103
|
+
<span class="count">${count}</span>
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
pill.addEventListener('click', () => {
|
|
107
|
+
chrome.runtime.sendMessage({ type: 'OPEN_SIDE_PANEL' });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
shadow.appendChild(style);
|
|
111
|
+
shadow.appendChild(pill);
|
|
112
|
+
document.body.appendChild(pillHost);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- Query brain after delay ----
|
|
116
|
+
function queryBrain() {
|
|
117
|
+
const query = pageContext.title || pageContext.url;
|
|
118
|
+
if (!query) return;
|
|
119
|
+
|
|
120
|
+
chrome.runtime.sendMessage(
|
|
121
|
+
{ type: 'QUERY_BRAIN', query, topK: 5 },
|
|
122
|
+
(response) => {
|
|
123
|
+
if (chrome.runtime.lastError) return;
|
|
124
|
+
const memories = response?.memories || [];
|
|
125
|
+
if (memories.length > 0) {
|
|
126
|
+
createPill(memories.length);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Wait 2 seconds after page load to avoid interfering with page rendering
|
|
133
|
+
setTimeout(queryBrain, 2000);
|
|
134
|
+
})();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Extension Icons
|
|
2
|
+
|
|
3
|
+
This directory should contain the following PNG icon files for the Total Recall Brain Extension:
|
|
4
|
+
|
|
5
|
+
| File | Size | Usage |
|
|
6
|
+
|-----------------|---------|---------------------------------------------|
|
|
7
|
+
| `icon-16.png` | 16×16 | Favicon, toolbar (small) |
|
|
8
|
+
| `icon-48.png` | 48×48 | Extensions management page |
|
|
9
|
+
| `icon-128.png` | 128×128 | Chrome Web Store, install dialog |
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Format: PNG with transparency
|
|
14
|
+
- Design: Should match Total Recall branding (brain / recall motif)
|
|
15
|
+
- The manifest references these exact filenames — do not rename without updating `manifest.json`
|
|
16
|
+
|
|
17
|
+
## Generating placeholder icons
|
|
18
|
+
|
|
19
|
+
You can generate minimal placeholder PNGs with ImageMagick:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
convert -size 16x16 xc:'#6366f1' icon-16.png
|
|
23
|
+
convert -size 48x48 xc:'#6366f1' icon-48.png
|
|
24
|
+
convert -size 128x128 xc:'#6366f1' icon-128.png
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Replace these with proper branded icons before publishing.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Brain API Client for Total Recall Chrome Extension
|
|
2
|
+
// Shared by background.js, popup, sidepanel, and options page.
|
|
3
|
+
// Exposed as `self.BrainClient` namespace (no ES-module bundler required).
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BRAIN_URL = 'http://127.0.0.1:3000';
|
|
6
|
+
|
|
7
|
+
async function getConfig() {
|
|
8
|
+
const config = await chrome.storage.sync.get(['brainUrl', 'pat']);
|
|
9
|
+
return {
|
|
10
|
+
brainUrl: config.brainUrl || DEFAULT_BRAIN_URL,
|
|
11
|
+
pat: config.pat || ''
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function brainFetch(path, options = {}) {
|
|
16
|
+
const { brainUrl, pat } = await getConfig();
|
|
17
|
+
const url = `${brainUrl}${path}`;
|
|
18
|
+
const headers = { 'Content-Type': 'application/json', ...options.headers };
|
|
19
|
+
if (pat) headers['Authorization'] = `Bearer ${pat}`;
|
|
20
|
+
|
|
21
|
+
const res = await fetch(url, { ...options, headers });
|
|
22
|
+
if (res.status === 401) {
|
|
23
|
+
chrome.action.setBadgeText({ text: '!' });
|
|
24
|
+
chrome.action.setBadgeBackgroundColor({ color: '#ef4444' });
|
|
25
|
+
throw new Error('Authentication failed — check your PAT token');
|
|
26
|
+
}
|
|
27
|
+
if (!res.ok) throw new Error(`Brain API error: ${res.status}`);
|
|
28
|
+
return res.json();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function share(data) {
|
|
32
|
+
return brainFetch('/api/share', { method: 'POST', body: JSON.stringify(data) });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function search(query, topK = 3) {
|
|
36
|
+
return brainFetch('/api/memory/search/semantic', {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
body: JSON.stringify({ query, top_k: topK })
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function healthCheck() {
|
|
43
|
+
return brainFetch('/health');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Expose as a global namespace for non-module contexts (service worker, popup, etc.)
|
|
47
|
+
self.BrainClient = { getConfig, brainFetch, share, search, healthCheck };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Total Recall — Brain Extension",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Contextual memory overlay, Share-to-Brain, and passive browsing context for Total Recall",
|
|
6
|
+
"permissions": ["activeTab", "contextMenus", "sidePanel", "storage"],
|
|
7
|
+
"optional_permissions": ["history", "webNavigation"],
|
|
8
|
+
"host_permissions": ["http://127.0.0.1:*/*", "http://localhost:*/*"],
|
|
9
|
+
"background": {
|
|
10
|
+
"service_worker": "background.js"
|
|
11
|
+
},
|
|
12
|
+
"action": {
|
|
13
|
+
"default_popup": "popup/popup.html",
|
|
14
|
+
"default_icon": {
|
|
15
|
+
"16": "icons/icon-16.png",
|
|
16
|
+
"48": "icons/icon-48.png",
|
|
17
|
+
"128": "icons/icon-128.png"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"side_panel": {
|
|
21
|
+
"default_path": "sidepanel/sidepanel.html"
|
|
22
|
+
},
|
|
23
|
+
"content_scripts": [{
|
|
24
|
+
"matches": ["<all_urls>"],
|
|
25
|
+
"js": ["content-script.js"],
|
|
26
|
+
"css": ["content-script.css"],
|
|
27
|
+
"run_at": "document_idle"
|
|
28
|
+
}],
|
|
29
|
+
"options_ui": {
|
|
30
|
+
"page": "options/options.html",
|
|
31
|
+
"open_in_tab": true
|
|
32
|
+
},
|
|
33
|
+
"incognito": "not_allowed",
|
|
34
|
+
"icons": {
|
|
35
|
+
"16": "icons/icon-16.png",
|
|
36
|
+
"48": "icons/icon-48.png",
|
|
37
|
+
"128": "icons/icon-128.png"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/* Total Recall — Options Page 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
|
+
min-height: 100vh;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.options-page {
|
|
17
|
+
max-width: 560px;
|
|
18
|
+
margin: 0 auto;
|
|
19
|
+
padding: 32px 24px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ---- Header ---- */
|
|
23
|
+
.options-header {
|
|
24
|
+
margin-bottom: 32px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.options-header h1 {
|
|
28
|
+
font-size: 20px;
|
|
29
|
+
font-weight: 700;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ---- Sections ---- */
|
|
33
|
+
.section {
|
|
34
|
+
margin-bottom: 28px;
|
|
35
|
+
padding: 20px;
|
|
36
|
+
background: #181825;
|
|
37
|
+
border-radius: 14px;
|
|
38
|
+
border: 1px solid #313244;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.section h2 {
|
|
42
|
+
font-size: 14px;
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
text-transform: uppercase;
|
|
45
|
+
letter-spacing: 0.8px;
|
|
46
|
+
color: #89b4fa;
|
|
47
|
+
margin-bottom: 16px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ---- Fields ---- */
|
|
51
|
+
.field-group {
|
|
52
|
+
margin-bottom: 16px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.field-group:last-child {
|
|
56
|
+
margin-bottom: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.field-group label {
|
|
60
|
+
display: block;
|
|
61
|
+
font-size: 13px;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
color: #a6adc8;
|
|
64
|
+
margin-bottom: 6px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.field-group .hint {
|
|
68
|
+
font-weight: 400;
|
|
69
|
+
color: #585b70;
|
|
70
|
+
font-size: 11px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.field-group input[type="text"],
|
|
74
|
+
.field-group input[type="password"],
|
|
75
|
+
.field-group textarea {
|
|
76
|
+
width: 100%;
|
|
77
|
+
padding: 10px 14px;
|
|
78
|
+
background: #1e1e2e;
|
|
79
|
+
border: 1px solid #313244;
|
|
80
|
+
border-radius: 10px;
|
|
81
|
+
color: #cdd6f4;
|
|
82
|
+
font-size: 14px;
|
|
83
|
+
outline: none;
|
|
84
|
+
font-family: inherit;
|
|
85
|
+
transition: border-color 0.2s ease;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.field-group input:focus,
|
|
89
|
+
.field-group textarea:focus {
|
|
90
|
+
border-color: #89b4fa;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.field-group textarea {
|
|
94
|
+
resize: vertical;
|
|
95
|
+
min-height: 80px;
|
|
96
|
+
font-family: 'SF Mono', Menlo, Monaco, monospace;
|
|
97
|
+
font-size: 13px;
|
|
98
|
+
line-height: 1.6;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ---- Connection test ---- */
|
|
102
|
+
.connection-row {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
gap: 12px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.connection-status {
|
|
109
|
+
font-size: 13px;
|
|
110
|
+
font-weight: 600;
|
|
111
|
+
transition: color 0.3s ease;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.connection-status.ok {
|
|
115
|
+
color: #a6e3a1;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.connection-status.fail {
|
|
119
|
+
color: #f38ba8;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ---- Radio group ---- */
|
|
123
|
+
.radio-group {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex-direction: column;
|
|
126
|
+
gap: 8px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.radio-label {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
gap: 10px;
|
|
133
|
+
padding: 10px 14px;
|
|
134
|
+
background: #1e1e2e;
|
|
135
|
+
border: 1px solid #313244;
|
|
136
|
+
border-radius: 10px;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
transition: border-color 0.2s ease;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.radio-label:hover {
|
|
142
|
+
border-color: #45475a;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.radio-label input[type="radio"] {
|
|
146
|
+
accent-color: #89b4fa;
|
|
147
|
+
width: 16px;
|
|
148
|
+
height: 16px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.radio-text {
|
|
152
|
+
font-size: 13px;
|
|
153
|
+
font-weight: 600;
|
|
154
|
+
color: #cdd6f4;
|
|
155
|
+
min-width: 60px;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.radio-desc {
|
|
159
|
+
font-size: 12px;
|
|
160
|
+
color: #585b70;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ---- Buttons ---- */
|
|
164
|
+
.btn {
|
|
165
|
+
padding: 10px 20px;
|
|
166
|
+
font-size: 13px;
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
border: 1px solid #313244;
|
|
169
|
+
border-radius: 10px;
|
|
170
|
+
cursor: pointer;
|
|
171
|
+
transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.btn:active {
|
|
175
|
+
transform: scale(0.97);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.btn.primary {
|
|
179
|
+
background: #89b4fa;
|
|
180
|
+
color: #11111b;
|
|
181
|
+
border-color: #89b4fa;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.btn.primary:hover {
|
|
185
|
+
background: #74c7ec;
|
|
186
|
+
border-color: #74c7ec;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.btn.secondary {
|
|
190
|
+
background: #1e1e2e;
|
|
191
|
+
color: #cdd6f4;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.btn.secondary:hover {
|
|
195
|
+
background: #313244;
|
|
196
|
+
border-color: #89b4fa;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* ---- Actions footer ---- */
|
|
200
|
+
.actions {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 14px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.save-status {
|
|
207
|
+
font-size: 13px;
|
|
208
|
+
font-weight: 600;
|
|
209
|
+
transition: opacity 0.3s ease;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.save-status.saved {
|
|
213
|
+
color: #a6e3a1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.save-status.error {
|
|
217
|
+
color: #f38ba8;
|
|
218
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Total Recall — Settings</title>
|
|
6
|
+
<link rel="stylesheet" href="options.css">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div class="options-page">
|
|
10
|
+
<header class="options-header">
|
|
11
|
+
<h1>🧠 Total Recall — Settings</h1>
|
|
12
|
+
</header>
|
|
13
|
+
|
|
14
|
+
<section class="section">
|
|
15
|
+
<h2>Connection</h2>
|
|
16
|
+
|
|
17
|
+
<div class="field-group">
|
|
18
|
+
<label for="brain-url">Brain URL</label>
|
|
19
|
+
<input type="text" id="brain-url" placeholder="http://127.0.0.1:3000" />
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="field-group">
|
|
23
|
+
<label for="pat-token">PAT Token</label>
|
|
24
|
+
<input type="password" id="pat-token" placeholder="Enter your personal access token" />
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="connection-row">
|
|
28
|
+
<button id="btn-test" class="btn secondary">Test Connection</button>
|
|
29
|
+
<div id="connection-status" class="connection-status"></div>
|
|
30
|
+
</div>
|
|
31
|
+
</section>
|
|
32
|
+
|
|
33
|
+
<section class="section">
|
|
34
|
+
<h2>Privacy</h2>
|
|
35
|
+
|
|
36
|
+
<div class="field-group">
|
|
37
|
+
<label for="blocklist">Domain Blocklist <span class="hint">(one per line)</span></label>
|
|
38
|
+
<textarea id="blocklist" rows="5" placeholder="example.com private.internal.io"></textarea>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="field-group">
|
|
42
|
+
<label>Capture Granularity</label>
|
|
43
|
+
<div class="radio-group">
|
|
44
|
+
<label class="radio-label">
|
|
45
|
+
<input type="radio" name="granularity" value="off" />
|
|
46
|
+
<span class="radio-text">Off</span>
|
|
47
|
+
<span class="radio-desc">No passive tracking</span>
|
|
48
|
+
</label>
|
|
49
|
+
<label class="radio-label">
|
|
50
|
+
<input type="radio" name="granularity" value="minimal" />
|
|
51
|
+
<span class="radio-text">Minimal</span>
|
|
52
|
+
<span class="radio-desc">URL and title only</span>
|
|
53
|
+
</label>
|
|
54
|
+
<label class="radio-label">
|
|
55
|
+
<input type="radio" name="granularity" value="full" />
|
|
56
|
+
<span class="radio-text">Full</span>
|
|
57
|
+
<span class="radio-desc">URL, title, and page metadata</span>
|
|
58
|
+
</label>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<div class="actions">
|
|
64
|
+
<button id="btn-save" class="btn primary">Save Settings</button>
|
|
65
|
+
<span id="save-status" class="save-status"></span>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<script src="../lib/brain-client.js"></script>
|
|
70
|
+
<script src="options.js"></script>
|
|
71
|
+
</body>
|
|
72
|
+
</html>
|