superlocalmemory 3.0.16 → 3.0.18
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/bin/slm-npm +8 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/cli/commands.py +29 -0
- package/src/superlocalmemory/cli/main.py +94 -30
- package/src/superlocalmemory/core/embedding_worker.py +120 -0
- package/src/superlocalmemory/core/embeddings.py +156 -240
- package/src/superlocalmemory/core/recall_worker.py +193 -0
- package/src/superlocalmemory/core/summarizer.py +182 -0
- package/src/superlocalmemory/core/worker_pool.py +209 -0
- package/src/superlocalmemory/mcp/server.py +9 -0
- package/src/superlocalmemory/mcp/tools_core.py +21 -8
- package/src/superlocalmemory/mcp/tools_v3.py +21 -0
- package/src/superlocalmemory/server/routes/helpers.py +21 -0
- package/src/superlocalmemory/server/routes/memories.py +100 -42
- package/src/superlocalmemory/server/routes/stats.py +11 -0
- package/src/superlocalmemory/server/routes/v3_api.py +195 -43
- package/src/superlocalmemory/server/ui.py +15 -14
- package/src/superlocalmemory/storage/database.py +23 -0
- package/src/superlocalmemory.egg-info/PKG-INFO +1 -1
- package/src/superlocalmemory.egg-info/SOURCES.txt +4 -0
- package/ui/index.html +113 -29
- package/ui/js/auto-settings.js +330 -1
- package/ui/js/clusters.js +138 -101
- package/ui/js/graph-core.js +3 -1
- package/ui/js/graph-interactions.js +2 -5
- package/ui/js/memories.js +65 -2
- package/ui/js/modal.js +79 -42
- package/ui/js/recall-lab.js +206 -60
package/ui/index.html
CHANGED
|
@@ -740,7 +740,7 @@
|
|
|
740
740
|
</li>
|
|
741
741
|
<li class="nav-item">
|
|
742
742
|
<button class="nav-link" id="learning-tab" data-bs-toggle="tab" data-bs-target="#learning-pane">
|
|
743
|
-
<i class="bi bi-mortarboard"></i> Learning
|
|
743
|
+
<i class="bi bi-mortarboard"></i> Learning
|
|
744
744
|
</button>
|
|
745
745
|
</li>
|
|
746
746
|
<li class="nav-item">
|
|
@@ -760,17 +760,17 @@
|
|
|
760
760
|
</li>
|
|
761
761
|
<li class="nav-item" role="presentation">
|
|
762
762
|
<button class="nav-link" id="lifecycle-tab" data-bs-toggle="tab" data-bs-target="#lifecycle-pane" type="button" role="tab">
|
|
763
|
-
<i class="bi bi-hourglass-split"></i> Lifecycle
|
|
763
|
+
<i class="bi bi-hourglass-split"></i> Lifecycle
|
|
764
764
|
</button>
|
|
765
765
|
</li>
|
|
766
766
|
<li class="nav-item" role="presentation">
|
|
767
767
|
<button class="nav-link" id="behavioral-tab" data-bs-toggle="tab" data-bs-target="#behavioral-pane" type="button" role="tab">
|
|
768
|
-
<i class="bi bi-lightbulb"></i> Behavioral
|
|
768
|
+
<i class="bi bi-lightbulb"></i> Behavioral
|
|
769
769
|
</button>
|
|
770
770
|
</li>
|
|
771
771
|
<li class="nav-item" role="presentation">
|
|
772
772
|
<button class="nav-link" id="compliance-tab" data-bs-toggle="tab" data-bs-target="#compliance-pane" type="button" role="tab">
|
|
773
|
-
<i class="bi bi-shield-lock"></i> Compliance
|
|
773
|
+
<i class="bi bi-shield-lock"></i> Compliance
|
|
774
774
|
</button>
|
|
775
775
|
</li>
|
|
776
776
|
<li class="nav-item">
|
|
@@ -1023,8 +1023,13 @@
|
|
|
1023
1023
|
<p class="text-muted">See how each retrieval channel contributes to results.</p>
|
|
1024
1024
|
<div class="input-group mb-3">
|
|
1025
1025
|
<input type="text" id="recall-lab-query" class="form-control form-control-lg" placeholder="Enter your query...">
|
|
1026
|
+
<select id="recall-lab-per-page" class="form-select" style="max-width:100px;">
|
|
1027
|
+
<option value="10" selected>10</option>
|
|
1028
|
+
<option value="25">25</option>
|
|
1029
|
+
<option value="50">50</option>
|
|
1030
|
+
</select>
|
|
1026
1031
|
<button class="btn btn-primary btn-lg" id="recall-lab-search">
|
|
1027
|
-
<i class="bi bi-search"></i> Search
|
|
1032
|
+
<i class="bi bi-search"></i> Search
|
|
1028
1033
|
</button>
|
|
1029
1034
|
</div>
|
|
1030
1035
|
<div id="recall-lab-meta" class="mb-3 text-muted small"></div>
|
|
@@ -1070,8 +1075,19 @@
|
|
|
1070
1075
|
</div>
|
|
1071
1076
|
</div>
|
|
1072
1077
|
|
|
1073
|
-
<!-- Learning System
|
|
1078
|
+
<!-- Learning System -->
|
|
1074
1079
|
<div class="tab-pane fade" id="learning-pane">
|
|
1080
|
+
<!-- Getting Started Guide -->
|
|
1081
|
+
<div class="alert alert-light border mb-3" id="learning-getting-started">
|
|
1082
|
+
<h6 class="mb-2"><i class="bi bi-info-circle text-primary"></i> How Learning Works</h6>
|
|
1083
|
+
<p class="mb-1 small">SuperLocalMemory learns from your usage patterns to improve retrieval ranking over time. This happens automatically as you use <code>slm recall</code> or search via MCP.</p>
|
|
1084
|
+
<ul class="small mb-0">
|
|
1085
|
+
<li><strong>0-20 signals:</strong> Baseline phase (collecting data)</li>
|
|
1086
|
+
<li><strong>20+ signals:</strong> Rule-based ranking adjustments</li>
|
|
1087
|
+
<li><strong>200+ signals:</strong> ML model trained on your patterns</li>
|
|
1088
|
+
</ul>
|
|
1089
|
+
<p class="mb-0 mt-1 small text-muted">Each recall query generates a signal. Keep using SLM and this tab will populate automatically.</p>
|
|
1090
|
+
</div>
|
|
1075
1091
|
<!-- Ranking Phase & Engagement -->
|
|
1076
1092
|
<div class="row g-3 mb-3">
|
|
1077
1093
|
<div class="col-md-4">
|
|
@@ -1424,12 +1440,18 @@
|
|
|
1424
1440
|
</div>
|
|
1425
1441
|
</div>
|
|
1426
1442
|
|
|
1427
|
-
<!-- Behavioral Learning
|
|
1443
|
+
<!-- Behavioral Learning -->
|
|
1428
1444
|
<div class="tab-pane fade" id="behavioral-pane" role="tabpanel">
|
|
1429
1445
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1430
1446
|
<h5 class="mb-0"><i class="bi bi-lightbulb text-info"></i> Behavioral Learning</h5>
|
|
1431
1447
|
<span class="badge bg-secondary" id="behavioral-profile-badge">default</span>
|
|
1432
1448
|
</div>
|
|
1449
|
+
<!-- Getting Started Guide -->
|
|
1450
|
+
<div class="alert alert-light border mb-3">
|
|
1451
|
+
<h6 class="mb-2"><i class="bi bi-info-circle text-primary"></i> How Behavioral Learning Works</h6>
|
|
1452
|
+
<p class="mb-1 small">This tab tracks how memories are used in practice — which recalls led to successful outcomes (code written, decisions made, bugs fixed) and which didn't.</p>
|
|
1453
|
+
<p class="mb-0 small text-muted">Report outcomes using the form below, or via MCP: <code>report_outcome</code>. Patterns emerge after 10+ outcomes.</p>
|
|
1454
|
+
</div>
|
|
1433
1455
|
<!-- Stats Row -->
|
|
1434
1456
|
<div class="row g-3 mb-4">
|
|
1435
1457
|
<div class="col-md-3"><div class="card p-3 text-center"><div class="fw-bold fs-3 text-success" id="bh-success-count">-</div><small class="text-muted">Successes</small></div></div>
|
|
@@ -1465,12 +1487,18 @@
|
|
|
1465
1487
|
</div>
|
|
1466
1488
|
</div>
|
|
1467
1489
|
|
|
1468
|
-
<!-- Compliance & Audit
|
|
1490
|
+
<!-- Compliance & Audit -->
|
|
1469
1491
|
<div class="tab-pane fade" id="compliance-pane" role="tabpanel">
|
|
1470
1492
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1471
1493
|
<h5 class="mb-0"><i class="bi bi-shield-lock text-success"></i> Compliance & Audit</h5>
|
|
1472
1494
|
<span class="badge bg-secondary" id="compliance-profile-badge">default</span>
|
|
1473
1495
|
</div>
|
|
1496
|
+
<!-- Getting Started Guide -->
|
|
1497
|
+
<div class="alert alert-light border mb-3">
|
|
1498
|
+
<h6 class="mb-2"><i class="bi bi-info-circle text-primary"></i> Compliance Overview</h6>
|
|
1499
|
+
<p class="mb-1 small">Set retention policies and access controls for your memories. In Mode A, all data stays on your device — EU AI Act compliant by default.</p>
|
|
1500
|
+
<p class="mb-0 small text-muted">Create a retention policy below to start managing memory lifecycle automatically.</p>
|
|
1501
|
+
</div>
|
|
1474
1502
|
<!-- Stats Row -->
|
|
1475
1503
|
<div class="row g-3 mb-4">
|
|
1476
1504
|
<div class="col-md-4"><div class="card p-3 text-center"><div class="fw-bold fs-3" id="cp-audit-count">-</div><small class="text-muted">Audit Events</small></div></div>
|
|
@@ -1541,30 +1569,86 @@
|
|
|
1541
1569
|
<h6 class="mb-0"><i class="bi bi-gear-wide-connected"></i> V3 Configuration</h6>
|
|
1542
1570
|
</div>
|
|
1543
1571
|
<div class="card-body">
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
<
|
|
1548
|
-
|
|
1549
|
-
<option value="b">Mode B — Smart Local (Ollama)</option>
|
|
1550
|
-
<option value="c">Mode C — Full Power (Cloud LLM)</option>
|
|
1551
|
-
</select>
|
|
1552
|
-
<button class="btn btn-sm btn-primary mt-2" id="settings-mode-save">Save Mode</button>
|
|
1572
|
+
<!-- Current Mode Banner -->
|
|
1573
|
+
<div id="settings-current-banner" class="alert alert-info mb-3">
|
|
1574
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
1575
|
+
<span>Active: <strong id="settings-current-mode">Loading...</strong></span>
|
|
1576
|
+
<span id="settings-current-detail" class="small"></span>
|
|
1553
1577
|
</div>
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1578
|
+
</div>
|
|
1579
|
+
|
|
1580
|
+
<!-- Step 1: Mode -->
|
|
1581
|
+
<div class="mb-3">
|
|
1582
|
+
<label class="form-label fw-bold">Step 1: Operating Mode</label>
|
|
1583
|
+
<div class="btn-group w-100" role="group">
|
|
1584
|
+
<input type="radio" class="btn-check" name="settings-mode-radio" id="mode-a-radio" value="a" checked>
|
|
1585
|
+
<label class="btn btn-outline-success" for="mode-a-radio">
|
|
1586
|
+
<strong>Mode A</strong><br><small>Zero Cloud — EU AI Act</small>
|
|
1587
|
+
</label>
|
|
1588
|
+
<input type="radio" class="btn-check" name="settings-mode-radio" id="mode-b-radio" value="b">
|
|
1589
|
+
<label class="btn btn-outline-info" for="mode-b-radio">
|
|
1590
|
+
<strong>Mode B</strong><br><small>Local Ollama LLM</small>
|
|
1591
|
+
</label>
|
|
1592
|
+
<input type="radio" class="btn-check" name="settings-mode-radio" id="mode-c-radio" value="c">
|
|
1593
|
+
<label class="btn btn-outline-warning" for="mode-c-radio">
|
|
1594
|
+
<strong>Mode C</strong><br><small>Cloud LLM (Best Accuracy)</small>
|
|
1595
|
+
</label>
|
|
1596
|
+
</div>
|
|
1597
|
+
</div>
|
|
1598
|
+
|
|
1599
|
+
<!-- Step 2: Provider Config (Mode B/C only) -->
|
|
1600
|
+
<div id="settings-provider-panel" style="display:none;" class="card p-3 mb-3 border-primary">
|
|
1601
|
+
<h6 class="mb-2">Step 2: LLM Configuration</h6>
|
|
1602
|
+
|
|
1603
|
+
<!-- Provider select -->
|
|
1604
|
+
<div class="row mb-2">
|
|
1605
|
+
<div class="col-md-4">
|
|
1606
|
+
<label class="form-label small fw-bold">Provider</label>
|
|
1607
|
+
<select id="settings-provider" class="form-select form-select-sm">
|
|
1608
|
+
<option value="">-- Select --</option>
|
|
1609
|
+
<option value="ollama">Ollama (Local)</option>
|
|
1610
|
+
<option value="openrouter">OpenRouter</option>
|
|
1611
|
+
<option value="openai">OpenAI</option>
|
|
1612
|
+
<option value="anthropic">Anthropic</option>
|
|
1613
|
+
</select>
|
|
1614
|
+
</div>
|
|
1615
|
+
<div class="col-md-4">
|
|
1616
|
+
<label class="form-label small fw-bold">Model <span class="text-danger">*</span></label>
|
|
1617
|
+
<select id="settings-model" class="form-select form-select-sm">
|
|
1618
|
+
<option value="">Select provider first</option>
|
|
1619
|
+
</select>
|
|
1620
|
+
<small id="settings-model-hint" class="text-muted"></small>
|
|
1621
|
+
</div>
|
|
1622
|
+
<div class="col-md-4" id="settings-key-col" style="display:none;">
|
|
1623
|
+
<label class="form-label small fw-bold">API Key <span class="text-danger">*</span></label>
|
|
1624
|
+
<input type="password" id="settings-api-key" class="form-control form-control-sm" placeholder="sk-... or your key">
|
|
1625
|
+
<small class="text-muted">Saved locally in ~/.superlocalmemory/</small>
|
|
1626
|
+
</div>
|
|
1627
|
+
</div>
|
|
1628
|
+
|
|
1629
|
+
<!-- Endpoint (advanced) -->
|
|
1630
|
+
<div class="row mb-2" id="settings-endpoint-row" style="display:none;">
|
|
1631
|
+
<div class="col-md-8">
|
|
1632
|
+
<label class="form-label small">API Endpoint (advanced)</label>
|
|
1633
|
+
<input type="text" id="settings-endpoint" class="form-control form-control-sm" placeholder="https://...">
|
|
1565
1634
|
</div>
|
|
1566
|
-
<button class="btn btn-sm btn-primary mt-2" id="settings-provider-save">Save Provider</button>
|
|
1567
1635
|
</div>
|
|
1636
|
+
|
|
1637
|
+
<!-- Connection test -->
|
|
1638
|
+
<div>
|
|
1639
|
+
<button class="btn btn-sm btn-outline-primary" id="settings-test-btn">
|
|
1640
|
+
<i class="bi bi-lightning"></i> Test Connection
|
|
1641
|
+
</button>
|
|
1642
|
+
<span id="settings-test-result" class="ms-2 small"></span>
|
|
1643
|
+
</div>
|
|
1644
|
+
</div>
|
|
1645
|
+
|
|
1646
|
+
<!-- Save button -->
|
|
1647
|
+
<div class="mt-2">
|
|
1648
|
+
<button class="btn btn-primary" id="settings-save-all">
|
|
1649
|
+
<i class="bi bi-check-circle"></i> Save Configuration
|
|
1650
|
+
</button>
|
|
1651
|
+
<span id="settings-save-status" class="ms-2" style="display:none;"></span>
|
|
1568
1652
|
</div>
|
|
1569
1653
|
<hr>
|
|
1570
1654
|
<div class="row">
|
package/ui/js/auto-settings.js
CHANGED
|
@@ -66,5 +66,334 @@ document.querySelectorAll('#auto-recall-toggle, #auto-recall-session').forEach(f
|
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Mode / Provider / Model Configuration (Professional Settings)
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
var PROVIDER_CONFIG = {
|
|
74
|
+
'ollama': {
|
|
75
|
+
name: 'Ollama',
|
|
76
|
+
needsKey: false,
|
|
77
|
+
endpoint: 'http://localhost:11434',
|
|
78
|
+
endpointEditable: true,
|
|
79
|
+
detectModels: true, // auto-detect via /api/v3/ollama/status
|
|
80
|
+
},
|
|
81
|
+
'openrouter': {
|
|
82
|
+
name: 'OpenRouter',
|
|
83
|
+
needsKey: true,
|
|
84
|
+
endpoint: 'https://openrouter.ai/api/v1',
|
|
85
|
+
endpointEditable: false,
|
|
86
|
+
},
|
|
87
|
+
'openai': {
|
|
88
|
+
name: 'OpenAI',
|
|
89
|
+
needsKey: true,
|
|
90
|
+
endpoint: 'https://api.openai.com/v1',
|
|
91
|
+
endpointEditable: true, // editable for Azure OpenAI
|
|
92
|
+
},
|
|
93
|
+
'anthropic': {
|
|
94
|
+
name: 'Anthropic',
|
|
95
|
+
needsKey: true,
|
|
96
|
+
endpoint: 'https://api.anthropic.com',
|
|
97
|
+
endpointEditable: false,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
var MODEL_OPTIONS = {
|
|
102
|
+
'none': [],
|
|
103
|
+
'ollama': [
|
|
104
|
+
{value: 'llama3.1:8b', label: 'Llama 3.1 8B'},
|
|
105
|
+
{value: 'llama3.2:latest', label: 'Llama 3.2'},
|
|
106
|
+
{value: 'qwen3-vl:8b', label: 'Qwen3 VL 8B'},
|
|
107
|
+
{value: 'mistral:latest', label: 'Mistral'},
|
|
108
|
+
],
|
|
109
|
+
'openrouter': [
|
|
110
|
+
{value: 'meta-llama/llama-3.1-8b-instruct:free', label: 'Llama 3.1 8B (Free)'},
|
|
111
|
+
{value: 'google/gemini-2.0-flash-001', label: 'Gemini 2.0 Flash'},
|
|
112
|
+
{value: 'anthropic/claude-3.5-haiku', label: 'Claude 3.5 Haiku'},
|
|
113
|
+
{value: 'openai/gpt-4o-mini', label: 'GPT-4o Mini'},
|
|
114
|
+
{value: 'deepseek/deepseek-chat-v3-0324:free', label: 'DeepSeek V3 (Free)'},
|
|
115
|
+
],
|
|
116
|
+
'openai': [
|
|
117
|
+
{value: 'gpt-4o-mini', label: 'GPT-4o Mini'},
|
|
118
|
+
{value: 'gpt-4o', label: 'GPT-4o'},
|
|
119
|
+
{value: 'gpt-4-turbo', label: 'GPT-4 Turbo'},
|
|
120
|
+
],
|
|
121
|
+
'anthropic': [
|
|
122
|
+
{value: 'claude-3-5-haiku-latest', label: 'Claude 3.5 Haiku'},
|
|
123
|
+
{value: 'claude-3-5-sonnet-latest', label: 'Claude 3.5 Sonnet'},
|
|
124
|
+
{value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6'},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
async function loadModeSettings() {
|
|
129
|
+
try {
|
|
130
|
+
var resp = await fetch('/api/v3/mode');
|
|
131
|
+
if (!resp.ok) return;
|
|
132
|
+
var data = await resp.json();
|
|
133
|
+
var mode = data.mode || 'a';
|
|
134
|
+
var provider = data.provider || 'none';
|
|
135
|
+
var model = data.model || '';
|
|
136
|
+
|
|
137
|
+
// Set radio button
|
|
138
|
+
var radio = document.getElementById('mode-' + mode + '-radio');
|
|
139
|
+
if (radio) radio.checked = true;
|
|
140
|
+
|
|
141
|
+
// Set provider dropdown
|
|
142
|
+
var provEl = document.getElementById('settings-provider');
|
|
143
|
+
if (provEl && provider !== 'none') provEl.value = provider;
|
|
144
|
+
|
|
145
|
+
// Update banner
|
|
146
|
+
var modeNames = {a: 'Mode A — Local Guardian', b: 'Mode B — Smart Local', c: 'Mode C — Full Power'};
|
|
147
|
+
var bannerMode = document.getElementById('settings-current-mode');
|
|
148
|
+
if (bannerMode) {
|
|
149
|
+
var label = modeNames[mode] || mode;
|
|
150
|
+
if (provider && provider !== 'none') label += ' | ' + provider;
|
|
151
|
+
if (model) label += ' | ' + model;
|
|
152
|
+
bannerMode.textContent = label;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
var bannerDetail = document.getElementById('settings-current-detail');
|
|
156
|
+
if (bannerDetail) {
|
|
157
|
+
if (mode === 'a') bannerDetail.textContent = 'Zero cloud — EU AI Act compliant';
|
|
158
|
+
else if (data.has_key) bannerDetail.textContent = 'API key configured';
|
|
159
|
+
else if (provider === 'ollama') bannerDetail.textContent = 'No API key needed';
|
|
160
|
+
else bannerDetail.textContent = 'API key not set';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
var banner = document.getElementById('settings-current-banner');
|
|
164
|
+
if (banner) {
|
|
165
|
+
banner.className = mode === 'a' ? 'alert alert-success mb-3' :
|
|
166
|
+
mode === 'b' ? 'alert alert-info mb-3' :
|
|
167
|
+
'alert alert-warning mb-3';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Show provider panel and populate model dropdown
|
|
171
|
+
updateModeUI();
|
|
172
|
+
|
|
173
|
+
// After provider UI updates, set the saved model value
|
|
174
|
+
if (model) {
|
|
175
|
+
setTimeout(function() {
|
|
176
|
+
var modelEl = document.getElementById('settings-model');
|
|
177
|
+
if (modelEl) {
|
|
178
|
+
// Check if option exists, if not add it
|
|
179
|
+
var found = false;
|
|
180
|
+
for (var i = 0; i < modelEl.options.length; i++) {
|
|
181
|
+
if (modelEl.options[i].value === model) { found = true; break; }
|
|
182
|
+
}
|
|
183
|
+
if (!found) {
|
|
184
|
+
var opt = document.createElement('option');
|
|
185
|
+
opt.value = model;
|
|
186
|
+
opt.textContent = model + ' (current)';
|
|
187
|
+
modelEl.insertBefore(opt, modelEl.firstChild);
|
|
188
|
+
}
|
|
189
|
+
modelEl.value = model;
|
|
190
|
+
}
|
|
191
|
+
}, 500);
|
|
192
|
+
}
|
|
193
|
+
} catch (e) {
|
|
194
|
+
console.log('Load mode settings error:', e);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function updateModeUI() {
|
|
199
|
+
var mode = document.querySelector('input[name="settings-mode-radio"]:checked')?.value || 'a';
|
|
200
|
+
var panel = document.getElementById('settings-provider-panel');
|
|
201
|
+
if (panel) {
|
|
202
|
+
panel.style.display = (mode === 'a') ? 'none' : 'block';
|
|
203
|
+
}
|
|
204
|
+
// Only set provider if it's currently empty (first load or Mode A→B/C)
|
|
205
|
+
var providerEl = document.getElementById('settings-provider');
|
|
206
|
+
if (providerEl && !providerEl.value) {
|
|
207
|
+
if (mode === 'b') providerEl.value = 'ollama';
|
|
208
|
+
}
|
|
209
|
+
if (mode !== 'a') updateProviderUI();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function updateProviderUI() {
|
|
213
|
+
var provider = document.getElementById('settings-provider')?.value || 'none';
|
|
214
|
+
var modelSelect = document.getElementById('settings-model');
|
|
215
|
+
var modelHint = document.getElementById('settings-model-hint');
|
|
216
|
+
|
|
217
|
+
// Preserve current model before rebuilding dropdown
|
|
218
|
+
var currentModel = modelSelect ? modelSelect.value : '';
|
|
219
|
+
|
|
220
|
+
var cfg = PROVIDER_CONFIG[provider] || {};
|
|
221
|
+
|
|
222
|
+
// Show/hide API key column
|
|
223
|
+
var keyCol = document.getElementById('settings-key-col');
|
|
224
|
+
if (keyCol) keyCol.style.display = cfg.needsKey ? 'block' : 'none';
|
|
225
|
+
|
|
226
|
+
// Show/hide endpoint row
|
|
227
|
+
var endpointRow = document.getElementById('settings-endpoint-row');
|
|
228
|
+
var endpointInput = document.getElementById('settings-endpoint');
|
|
229
|
+
if (endpointRow) {
|
|
230
|
+
endpointRow.style.display = cfg.endpointEditable ? 'block' : 'none';
|
|
231
|
+
if (endpointInput && cfg.endpoint) endpointInput.value = cfg.endpoint;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// For Ollama: check live status and populate real models
|
|
235
|
+
if (provider === 'ollama') {
|
|
236
|
+
if (modelHint) modelHint.textContent = 'Checking Ollama...';
|
|
237
|
+
fetch('/api/v3/ollama/status').then(function(r) { return r.json(); }).then(function(data) {
|
|
238
|
+
if (modelSelect) {
|
|
239
|
+
modelSelect.textContent = '';
|
|
240
|
+
if (data.running && data.models.length > 0) {
|
|
241
|
+
data.models.forEach(function(m) {
|
|
242
|
+
var opt = document.createElement('option');
|
|
243
|
+
opt.value = m.name;
|
|
244
|
+
opt.textContent = m.name;
|
|
245
|
+
modelSelect.appendChild(opt);
|
|
246
|
+
});
|
|
247
|
+
if (modelHint) modelHint.textContent = 'Ollama running (' + data.count + ' models)';
|
|
248
|
+
if (modelHint) modelHint.className = 'text-success small';
|
|
249
|
+
} else {
|
|
250
|
+
var opt = document.createElement('option');
|
|
251
|
+
opt.value = '';
|
|
252
|
+
opt.textContent = 'Ollama not running!';
|
|
253
|
+
modelSelect.appendChild(opt);
|
|
254
|
+
if (modelHint) modelHint.textContent = 'Ollama not detected. Run: ollama serve';
|
|
255
|
+
if (modelHint) modelHint.className = 'text-danger small';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}).catch(function() {
|
|
259
|
+
if (modelHint) { modelHint.textContent = 'Ollama not reachable'; modelHint.className = 'text-danger small'; }
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// For other providers: use static model list
|
|
265
|
+
if (modelSelect) {
|
|
266
|
+
modelSelect.textContent = '';
|
|
267
|
+
var options = MODEL_OPTIONS[provider] || [];
|
|
268
|
+
if (options.length === 0) {
|
|
269
|
+
var opt = document.createElement('option');
|
|
270
|
+
opt.value = '';
|
|
271
|
+
opt.textContent = 'N/A (Mode A)';
|
|
272
|
+
modelSelect.appendChild(opt);
|
|
273
|
+
} else {
|
|
274
|
+
options.forEach(function(o) {
|
|
275
|
+
var opt = document.createElement('option');
|
|
276
|
+
opt.value = o.value;
|
|
277
|
+
opt.textContent = o.label;
|
|
278
|
+
modelSelect.appendChild(opt);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
// Restore previous model selection if it exists in the new list
|
|
282
|
+
if (currentModel) {
|
|
283
|
+
var found = false;
|
|
284
|
+
for (var i = 0; i < modelSelect.options.length; i++) {
|
|
285
|
+
if (modelSelect.options[i].value === currentModel) { found = true; break; }
|
|
286
|
+
}
|
|
287
|
+
if (found) {
|
|
288
|
+
modelSelect.value = currentModel;
|
|
289
|
+
} else if (currentModel) {
|
|
290
|
+
// Model not in list — add it as custom option so user doesn't lose their choice
|
|
291
|
+
var custom = document.createElement('option');
|
|
292
|
+
custom.value = currentModel;
|
|
293
|
+
custom.textContent = currentModel + ' (saved)';
|
|
294
|
+
modelSelect.insertBefore(custom, modelSelect.firstChild);
|
|
295
|
+
modelSelect.value = currentModel;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Update hint
|
|
301
|
+
if (modelHint) {
|
|
302
|
+
var hints = {
|
|
303
|
+
'none': 'No LLM needed in Mode A',
|
|
304
|
+
'openrouter': '200+ models via OpenRouter API',
|
|
305
|
+
'openai': 'OpenAI models (requires API key)',
|
|
306
|
+
'anthropic': 'Anthropic models (requires API key)',
|
|
307
|
+
};
|
|
308
|
+
modelHint.textContent = hints[provider] || '';
|
|
309
|
+
modelHint.className = 'text-muted small';
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function testConnection() {
|
|
314
|
+
var provider = document.getElementById('settings-provider')?.value || '';
|
|
315
|
+
var model = document.getElementById('settings-model')?.value || '';
|
|
316
|
+
var apiKey = document.getElementById('settings-api-key')?.value || '';
|
|
317
|
+
var resultEl = document.getElementById('settings-test-result');
|
|
318
|
+
|
|
319
|
+
if (!provider) {
|
|
320
|
+
if (resultEl) { resultEl.textContent = 'Select a provider first'; resultEl.className = 'ms-2 small text-danger'; }
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (resultEl) { resultEl.textContent = 'Testing...'; resultEl.className = 'ms-2 small text-muted'; }
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
var resp = await fetch('/api/v3/provider/test', {
|
|
328
|
+
method: 'POST',
|
|
329
|
+
headers: {'Content-Type': 'application/json'},
|
|
330
|
+
body: JSON.stringify({provider: provider, model: model, api_key: apiKey})
|
|
331
|
+
});
|
|
332
|
+
var data = await resp.json();
|
|
333
|
+
if (data.success) {
|
|
334
|
+
if (resultEl) { resultEl.textContent = 'Connected! ' + (data.message || ''); resultEl.className = 'ms-2 small text-success fw-bold'; }
|
|
335
|
+
} else {
|
|
336
|
+
if (resultEl) { resultEl.textContent = 'Failed: ' + (data.error || 'Unknown'); resultEl.className = 'ms-2 small text-danger'; }
|
|
337
|
+
}
|
|
338
|
+
} catch (e) {
|
|
339
|
+
if (resultEl) { resultEl.textContent = 'Error: ' + e.message; resultEl.className = 'ms-2 small text-danger'; }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function saveAllSettings() {
|
|
344
|
+
var mode = document.querySelector('input[name="settings-mode-radio"]:checked')?.value || 'a';
|
|
345
|
+
var provider = document.getElementById('settings-provider')?.value || 'none';
|
|
346
|
+
if (mode === 'a') provider = 'none';
|
|
347
|
+
var model = document.getElementById('settings-model')?.value || '';
|
|
348
|
+
var apiKey = document.getElementById('settings-api-key')?.value || '';
|
|
349
|
+
|
|
350
|
+
var statusEl = document.getElementById('settings-save-status');
|
|
351
|
+
var saveBtn = document.getElementById('settings-save-all');
|
|
352
|
+
if (saveBtn) saveBtn.disabled = true;
|
|
353
|
+
if (statusEl) { statusEl.textContent = 'Saving...'; statusEl.style.display = 'inline'; statusEl.className = 'ms-2 text-muted'; }
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
// Save mode
|
|
357
|
+
var modeResp = await fetch('/api/v3/mode/set', {
|
|
358
|
+
method: 'POST',
|
|
359
|
+
headers: {'Content-Type': 'application/json'},
|
|
360
|
+
body: JSON.stringify({mode: mode, provider: provider, model: model, api_key: apiKey})
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
if (modeResp.ok) {
|
|
364
|
+
if (statusEl) {
|
|
365
|
+
statusEl.textContent = 'Configuration saved! Mode: ' + mode.toUpperCase() +
|
|
366
|
+
(provider !== 'none' ? ' | Provider: ' + provider : '');
|
|
367
|
+
statusEl.className = 'ms-2 text-success fw-bold';
|
|
368
|
+
}
|
|
369
|
+
loadModeSettings();
|
|
370
|
+
} else {
|
|
371
|
+
if (statusEl) { statusEl.textContent = 'Save failed'; statusEl.className = 'ms-2 text-danger'; }
|
|
372
|
+
}
|
|
373
|
+
} catch (e) {
|
|
374
|
+
if (statusEl) { statusEl.textContent = 'Error: ' + e.message; statusEl.className = 'ms-2 text-danger'; }
|
|
375
|
+
}
|
|
376
|
+
if (saveBtn) saveBtn.disabled = false;
|
|
377
|
+
|
|
378
|
+
// Auto-hide status after 5 seconds
|
|
379
|
+
setTimeout(function() {
|
|
380
|
+
if (statusEl) statusEl.style.display = 'none';
|
|
381
|
+
}, 5000);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Bind events
|
|
385
|
+
document.getElementById('settings-provider')?.addEventListener('change', updateProviderUI);
|
|
386
|
+
document.getElementById('settings-save-all')?.addEventListener('click', saveAllSettings);
|
|
387
|
+
document.getElementById('settings-test-btn')?.addEventListener('click', testConnection);
|
|
388
|
+
|
|
389
|
+
// Mode radio buttons
|
|
390
|
+
document.querySelectorAll('input[name="settings-mode-radio"]').forEach(function(radio) {
|
|
391
|
+
radio.addEventListener('change', updateModeUI);
|
|
392
|
+
});
|
|
393
|
+
|
|
69
394
|
// Load settings when the settings tab is shown
|
|
70
|
-
document.getElementById('settings-tab')?.addEventListener('shown.bs.tab',
|
|
395
|
+
document.getElementById('settings-tab')?.addEventListener('shown.bs.tab', function() {
|
|
396
|
+
loadAutoSettings();
|
|
397
|
+
loadModeSettings();
|
|
398
|
+
updateModeUI();
|
|
399
|
+
});
|