@199-bio/engram 0.7.2 → 0.8.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/src/web/server.ts CHANGED
@@ -13,6 +13,7 @@ import { KnowledgeGraph } from "../graph/knowledge-graph.js";
13
13
  import { HybridSearch } from "../retrieval/hybrid.js";
14
14
  import { ChatHandler } from "./chat-handler.js";
15
15
  import { Consolidator } from "../consolidation/consolidator.js";
16
+ import { loadSettings, saveSettings, hasAnthropicApiKey } from "../settings.js";
16
17
 
17
18
  const __filename = fileURLToPath(import.meta.url);
18
19
  const __dirname = path.dirname(__filename);
@@ -240,9 +241,9 @@ export class EngramWebServer {
240
241
  const offset = parseInt(url.searchParams.get("offset") || "0");
241
242
 
242
243
  if (query) {
243
- const results = await this.search.search(query, { limit });
244
+ const response = await this.search.search(query, { limit });
244
245
  res.end(JSON.stringify({
245
- memories: results.map(r => ({
246
+ memories: response.results.map(r => ({
246
247
  ...r.memory,
247
248
  score: r.score,
248
249
  sources: r.sources,
@@ -379,13 +380,58 @@ export class EngramWebServer {
379
380
  return;
380
381
  }
381
382
 
383
+ // ============ Settings Endpoints ============
384
+
385
+ // GET /api/settings - get current settings (without exposing full API key)
386
+ if (pathname === "/api/settings" && method === "GET") {
387
+ const settings = loadSettings();
388
+ res.end(JSON.stringify({
389
+ has_api_key: hasAnthropicApiKey(),
390
+ api_key_preview: settings.anthropic_api_key
391
+ ? `${settings.anthropic_api_key.slice(0, 12)}...${settings.anthropic_api_key.slice(-4)}`
392
+ : null,
393
+ api_key_source: settings.anthropic_api_key
394
+ ? "settings"
395
+ : process.env.ANTHROPIC_API_KEY
396
+ ? "environment"
397
+ : null,
398
+ }));
399
+ return;
400
+ }
401
+
402
+ // POST /api/settings - update settings
403
+ if (pathname === "/api/settings" && method === "POST") {
404
+ const { anthropic_api_key } = body as { anthropic_api_key?: string };
405
+
406
+ if (anthropic_api_key !== undefined) {
407
+ const settings = loadSettings();
408
+ if (anthropic_api_key === "") {
409
+ // Clear the API key
410
+ delete settings.anthropic_api_key;
411
+ } else {
412
+ settings.anthropic_api_key = anthropic_api_key;
413
+ }
414
+ saveSettings(settings);
415
+
416
+ // Refresh the chat client
417
+ this.chat.refreshClient();
418
+ this.consolidator = new Consolidator(this.db); // Reinit consolidator
419
+ }
420
+
421
+ res.end(JSON.stringify({
422
+ success: true,
423
+ configured: this.chat.isConfigured(),
424
+ }));
425
+ return;
426
+ }
427
+
382
428
  // GET /api/chat/status - check if chat is configured
383
429
  if (pathname === "/api/chat/status" && method === "GET") {
384
430
  res.end(JSON.stringify({
385
431
  configured: this.chat.isConfigured(),
386
432
  message: this.chat.isConfigured()
387
433
  ? "Chat is ready"
388
- : "Set ANTHROPIC_API_KEY environment variable to enable chat",
434
+ : "Configure API key in Settings",
389
435
  }));
390
436
  return;
391
437
  }
@@ -17,6 +17,7 @@ const views = {
17
17
  entities: document.getElementById('entities-view'),
18
18
  graph: document.getElementById('graph-view'),
19
19
  consolidation: document.getElementById('consolidation-view'),
20
+ settings: document.getElementById('settings-view'),
20
21
  };
21
22
 
22
23
  const statsEl = document.getElementById('stats');
@@ -352,6 +353,7 @@ function switchView(view) {
352
353
  if (view === 'entities') loadEntities(entityTypeFilter.value);
353
354
  if (view === 'graph') loadGraph();
354
355
  if (view === 'consolidation') loadConsolidation();
356
+ if (view === 'settings') loadSettings();
355
357
  }
356
358
 
357
359
  // ============ Consolidation ============
@@ -881,6 +883,104 @@ async function checkApiStatus() {
881
883
  }
882
884
  }
883
885
 
886
+ // ============ Settings ============
887
+
888
+ const apiStatusBadge = document.getElementById('api-status-badge');
889
+ const apiKeyInput = document.getElementById('api-key-input');
890
+ const toggleKeyVisibility = document.getElementById('toggle-key-visibility');
891
+ const saveApiKeyBtn = document.getElementById('save-api-key');
892
+ const clearApiKeyBtn = document.getElementById('clear-api-key');
893
+
894
+ async function loadSettings() {
895
+ try {
896
+ const settings = await api('/api/settings');
897
+ updateSettingsUI(settings);
898
+ } catch (e) {
899
+ console.error('Failed to load settings', e);
900
+ }
901
+ }
902
+
903
+ function updateSettingsUI(settings) {
904
+ if (settings.has_api_key) {
905
+ apiStatusBadge.textContent = `Configured (${settings.api_key_source})`;
906
+ apiStatusBadge.className = 'status-badge configured';
907
+ apiKeyInput.placeholder = settings.api_key_preview || 'sk-ant-api03-...';
908
+ apiKeyInput.value = '';
909
+ } else {
910
+ apiStatusBadge.textContent = 'Not configured';
911
+ apiStatusBadge.className = 'status-badge not-configured';
912
+ apiKeyInput.placeholder = 'sk-ant-api03-...';
913
+ }
914
+ }
915
+
916
+ if (toggleKeyVisibility) {
917
+ toggleKeyVisibility.addEventListener('click', () => {
918
+ const type = apiKeyInput.type === 'password' ? 'text' : 'password';
919
+ apiKeyInput.type = type;
920
+ toggleKeyVisibility.textContent = type === 'password' ? '👁' : '🙈';
921
+ });
922
+ }
923
+
924
+ if (saveApiKeyBtn) {
925
+ saveApiKeyBtn.addEventListener('click', async () => {
926
+ const apiKey = apiKeyInput.value.trim();
927
+ if (!apiKey) {
928
+ alert('Please enter an API key');
929
+ return;
930
+ }
931
+
932
+ if (!apiKey.startsWith('sk-ant-')) {
933
+ alert('Invalid API key format. Should start with sk-ant-');
934
+ return;
935
+ }
936
+
937
+ try {
938
+ saveApiKeyBtn.disabled = true;
939
+ saveApiKeyBtn.textContent = 'Saving...';
940
+
941
+ const result = await api('/api/settings', {
942
+ method: 'POST',
943
+ body: { anthropic_api_key: apiKey },
944
+ });
945
+
946
+ if (result.success) {
947
+ apiKeyInput.value = '';
948
+ await loadSettings();
949
+ await checkApiStatus();
950
+ alert('API key saved successfully!');
951
+ } else {
952
+ alert('Failed to save API key');
953
+ }
954
+ } catch (e) {
955
+ console.error('Failed to save API key', e);
956
+ alert('Error saving API key');
957
+ } finally {
958
+ saveApiKeyBtn.disabled = false;
959
+ saveApiKeyBtn.textContent = 'Save API Key';
960
+ }
961
+ });
962
+ }
963
+
964
+ if (clearApiKeyBtn) {
965
+ clearApiKeyBtn.addEventListener('click', async () => {
966
+ if (!confirm('Are you sure you want to clear the API key?')) return;
967
+
968
+ try {
969
+ clearApiKeyBtn.disabled = true;
970
+ await api('/api/settings', {
971
+ method: 'POST',
972
+ body: { anthropic_api_key: '' },
973
+ });
974
+ await loadSettings();
975
+ await checkApiStatus();
976
+ } catch (e) {
977
+ console.error('Failed to clear API key', e);
978
+ } finally {
979
+ clearApiKeyBtn.disabled = false;
980
+ }
981
+ });
982
+ }
983
+
884
984
  // Initialize
885
985
  checkApiStatus();
886
986
  loadStats();
@@ -14,6 +14,7 @@
14
14
  <button class="nav-btn" data-view="entities">Entities</button>
15
15
  <button class="nav-btn" data-view="graph">Graph</button>
16
16
  <button class="nav-btn" data-view="consolidation">Consolidation</button>
17
+ <button class="nav-btn" data-view="settings">Settings</button>
17
18
  </nav>
18
19
  <div class="stats" id="stats"></div>
19
20
  <div class="api-status" id="api-status" title="Anthropic API Status">
@@ -59,6 +60,40 @@
59
60
  <div id="graph-container"></div>
60
61
  </section>
61
62
 
63
+ <!-- Settings View -->
64
+ <section id="settings-view" class="view">
65
+ <div class="settings-container">
66
+ <h2>Settings</h2>
67
+
68
+ <div class="settings-section">
69
+ <h3>API Configuration</h3>
70
+ <p class="section-desc">Configure your Anthropic API key to enable Chat and Consolidation features.</p>
71
+
72
+ <div class="settings-row" id="api-key-status">
73
+ <label>Status:</label>
74
+ <span class="status-badge" id="api-status-badge">Checking...</span>
75
+ </div>
76
+
77
+ <div class="settings-row">
78
+ <label for="api-key-input">API Key:</label>
79
+ <div class="input-group">
80
+ <input type="password" id="api-key-input" placeholder="sk-ant-api03-..." autocomplete="off">
81
+ <button type="button" id="toggle-key-visibility" title="Show/hide">👁</button>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="settings-row">
86
+ <button id="save-api-key" class="primary-btn">Save API Key</button>
87
+ <button id="clear-api-key" class="danger-btn">Clear</button>
88
+ </div>
89
+
90
+ <p class="help-text">
91
+ Get your API key from <a href="https://console.anthropic.com/settings/keys" target="_blank">console.anthropic.com</a>
92
+ </p>
93
+ </div>
94
+ </div>
95
+ </section>
96
+
62
97
  <!-- Consolidation View -->
63
98
  <section id="consolidation-view" class="view">
64
99
  <div class="consolidation-header">