superlocalmemory 3.4.23 → 3.4.24
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/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/core/config.py +66 -18
- package/src/superlocalmemory/core/embedding_worker.py +8 -27
- package/src/superlocalmemory/core/embeddings.py +83 -1
- package/src/superlocalmemory/core/engine_wiring.py +8 -0
- package/src/superlocalmemory/core/platform_utils.py +127 -0
- package/src/superlocalmemory/core/recall_worker.py +8 -24
- package/src/superlocalmemory/core/reranker_worker.py +8 -24
- package/src/superlocalmemory/core/worker_pool.py +2 -1
- package/src/superlocalmemory/retrieval/reranker.py +2 -1
- package/src/superlocalmemory/server/routes/v3_api.py +150 -8
- package/src/superlocalmemory/ui/index.html +46 -1
- package/src/superlocalmemory/ui/js/auto-settings.js +131 -5
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -655
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -426
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -58
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -129,6 +129,11 @@ async def set_mode(request: Request):
|
|
|
129
129
|
llm_model=old_config.llm.model,
|
|
130
130
|
llm_api_key=old_config.llm.api_key,
|
|
131
131
|
llm_api_base=old_config.llm.api_base,
|
|
132
|
+
embedding_provider=old_config.embedding.provider,
|
|
133
|
+
embedding_endpoint=old_config.embedding.api_endpoint,
|
|
134
|
+
embedding_key=old_config.embedding.api_key,
|
|
135
|
+
embedding_model_name=old_config.embedding.model_name,
|
|
136
|
+
embedding_dimension=old_config.embedding.dimension,
|
|
132
137
|
)
|
|
133
138
|
new_config.active_profile = old_config.active_profile
|
|
134
139
|
new_config.save()
|
|
@@ -165,7 +170,10 @@ async def set_mode(request: Request):
|
|
|
165
170
|
|
|
166
171
|
@router.post("/mode/set")
|
|
167
172
|
async def set_full_config(request: Request):
|
|
168
|
-
"""Save mode + provider + model + API key together.
|
|
173
|
+
"""Save mode + provider + model + API key together.
|
|
174
|
+
|
|
175
|
+
V3.4.24: Also accepts embedding_* fields for custom embedding endpoints.
|
|
176
|
+
"""
|
|
169
177
|
try:
|
|
170
178
|
body = await request.json()
|
|
171
179
|
new_mode = body.get("mode", "a").lower()
|
|
@@ -187,6 +195,11 @@ async def set_full_config(request: Request):
|
|
|
187
195
|
llm_model=model,
|
|
188
196
|
llm_api_key=api_key,
|
|
189
197
|
llm_api_base="http://localhost:11434" if provider == "ollama" else "",
|
|
198
|
+
embedding_provider=body.get("embedding_provider", ""),
|
|
199
|
+
embedding_endpoint=body.get("embedding_endpoint", ""),
|
|
200
|
+
embedding_key=body.get("embedding_key", ""),
|
|
201
|
+
embedding_model_name=body.get("embedding_model", ""),
|
|
202
|
+
embedding_dimension=int(body.get("embedding_dimension", 0) or 0),
|
|
190
203
|
)
|
|
191
204
|
config.active_profile = old.active_profile
|
|
192
205
|
config.save()
|
|
@@ -213,11 +226,145 @@ async def set_full_config(request: Request):
|
|
|
213
226
|
"mode": new_mode,
|
|
214
227
|
"provider": provider,
|
|
215
228
|
"model": model,
|
|
229
|
+
"embedding_provider": config.embedding.provider,
|
|
230
|
+
"embedding_model": config.embedding.model_name,
|
|
231
|
+
"embedding_dimension": config.embedding.dimension,
|
|
232
|
+
}
|
|
233
|
+
except Exception as e:
|
|
234
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# ── V3.4.24: Embedding Configuration ────────────────────────────────
|
|
238
|
+
|
|
239
|
+
@router.get("/embedding/config")
|
|
240
|
+
async def get_embedding_config(request: Request):
|
|
241
|
+
"""Return current embedding configuration."""
|
|
242
|
+
try:
|
|
243
|
+
from superlocalmemory.core.config import SLMConfig
|
|
244
|
+
config = SLMConfig.load()
|
|
245
|
+
emb = config.embedding
|
|
246
|
+
return {
|
|
247
|
+
"provider": emb.provider,
|
|
248
|
+
"model_name": emb.model_name,
|
|
249
|
+
"dimension": emb.dimension,
|
|
250
|
+
"api_endpoint": emb.api_endpoint,
|
|
251
|
+
"has_key": bool(emb.api_key),
|
|
252
|
+
"is_openai_compatible": emb.is_openai_compatible,
|
|
253
|
+
"mode": config.mode.value,
|
|
254
|
+
}
|
|
255
|
+
except Exception as e:
|
|
256
|
+
return JSONResponse({"error": str(e)}, status_code=500)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@router.put("/embedding/config")
|
|
260
|
+
async def set_embedding_config(request: Request):
|
|
261
|
+
"""Update embedding configuration independently of mode switch."""
|
|
262
|
+
try:
|
|
263
|
+
body = await request.json()
|
|
264
|
+
from superlocalmemory.core.config import SLMConfig, EmbeddingConfig
|
|
265
|
+
config = SLMConfig.load()
|
|
266
|
+
|
|
267
|
+
new_provider = body.get("provider", config.embedding.provider)
|
|
268
|
+
new_model = body.get("model_name", config.embedding.model_name)
|
|
269
|
+
new_dim = int(body.get("dimension", config.embedding.dimension) or 768)
|
|
270
|
+
if not (64 <= new_dim <= 8192):
|
|
271
|
+
return JSONResponse({"error": f"Dimension must be 64-8192, got {new_dim}"}, status_code=400)
|
|
272
|
+
new_endpoint = body.get("api_endpoint", config.embedding.api_endpoint)
|
|
273
|
+
new_key = body.get("api_key", config.embedding.api_key)
|
|
274
|
+
|
|
275
|
+
old_emb = config.embedding
|
|
276
|
+
config.embedding = EmbeddingConfig(
|
|
277
|
+
model_name=new_model,
|
|
278
|
+
dimension=new_dim,
|
|
279
|
+
provider=new_provider,
|
|
280
|
+
api_endpoint=new_endpoint,
|
|
281
|
+
api_key=new_key,
|
|
282
|
+
ollama_model=old_emb.ollama_model,
|
|
283
|
+
ollama_base_url=old_emb.ollama_base_url,
|
|
284
|
+
api_version=old_emb.api_version,
|
|
285
|
+
deployment_name=old_emb.deployment_name,
|
|
286
|
+
)
|
|
287
|
+
config.save()
|
|
288
|
+
|
|
289
|
+
needs_reindex = (
|
|
290
|
+
old_emb.provider != new_provider
|
|
291
|
+
or old_emb.model_name != new_model
|
|
292
|
+
or old_emb.dimension != new_dim
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Kill workers so next request uses new config
|
|
296
|
+
try:
|
|
297
|
+
from superlocalmemory.core.worker_pool import WorkerPool
|
|
298
|
+
WorkerPool.shared().shutdown()
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
if hasattr(request.app.state, "engine"):
|
|
302
|
+
request.app.state.engine = None
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
"success": True,
|
|
306
|
+
"provider": new_provider,
|
|
307
|
+
"model_name": new_model,
|
|
308
|
+
"dimension": new_dim,
|
|
309
|
+
"needs_reindex": needs_reindex,
|
|
216
310
|
}
|
|
217
311
|
except Exception as e:
|
|
218
312
|
return JSONResponse({"error": str(e)}, status_code=500)
|
|
219
313
|
|
|
220
314
|
|
|
315
|
+
@router.post("/embedding/test")
|
|
316
|
+
async def test_embedding_endpoint(request: Request):
|
|
317
|
+
"""Test connectivity to a custom embedding endpoint."""
|
|
318
|
+
try:
|
|
319
|
+
import httpx
|
|
320
|
+
from urllib.parse import urlparse
|
|
321
|
+
body = await request.json()
|
|
322
|
+
endpoint = body.get("api_endpoint", "").rstrip("/")
|
|
323
|
+
model = body.get("model_name", "test")
|
|
324
|
+
api_key = body.get("api_key", "")
|
|
325
|
+
|
|
326
|
+
if not endpoint:
|
|
327
|
+
return JSONResponse({"error": "No endpoint provided"}, status_code=400)
|
|
328
|
+
|
|
329
|
+
parsed = urlparse(endpoint)
|
|
330
|
+
if parsed.scheme not in ("http", "https"):
|
|
331
|
+
return JSONResponse({"error": "Only http/https endpoints supported"}, status_code=400)
|
|
332
|
+
host = parsed.hostname or ""
|
|
333
|
+
if host in ("169.254.169.254", "metadata.google.internal"):
|
|
334
|
+
return JSONResponse({"error": "Cloud metadata endpoints not allowed"}, status_code=400)
|
|
335
|
+
|
|
336
|
+
if not endpoint.endswith("/embeddings"):
|
|
337
|
+
endpoint = f"{endpoint}/embeddings"
|
|
338
|
+
|
|
339
|
+
headers = {"Content-Type": "application/json"}
|
|
340
|
+
if api_key:
|
|
341
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
342
|
+
|
|
343
|
+
payload = {"input": ["test embedding connection"], "model": model}
|
|
344
|
+
|
|
345
|
+
with httpx.Client(timeout=httpx.Timeout(15.0)) as client:
|
|
346
|
+
resp = client.post(endpoint, headers=headers, json=payload)
|
|
347
|
+
resp.raise_for_status()
|
|
348
|
+
data = resp.json()
|
|
349
|
+
emb_data = data.get("data", [])
|
|
350
|
+
if emb_data:
|
|
351
|
+
dim = len(emb_data[0].get("embedding", []))
|
|
352
|
+
return {
|
|
353
|
+
"success": True,
|
|
354
|
+
"message": f"Connected! Dimension: {dim}",
|
|
355
|
+
"dimension": dim,
|
|
356
|
+
}
|
|
357
|
+
return {"success": False, "error": "No embedding data returned"}
|
|
358
|
+
except httpx.HTTPStatusError as e:
|
|
359
|
+
return {"success": False, "error": f"HTTP {e.response.status_code}"}
|
|
360
|
+
except httpx.ConnectError:
|
|
361
|
+
return {"success": False, "error": "Cannot reach the embedding server. Is it running?"}
|
|
362
|
+
except httpx.TimeoutException:
|
|
363
|
+
return {"success": False, "error": "Connection timed out after 15 seconds."}
|
|
364
|
+
except Exception as e:
|
|
365
|
+
return {"success": False, "error": type(e).__name__}
|
|
366
|
+
|
|
367
|
+
|
|
221
368
|
@router.post("/provider/test")
|
|
222
369
|
async def test_provider(request: Request):
|
|
223
370
|
"""Test connectivity to an LLM provider."""
|
|
@@ -1593,13 +1740,8 @@ async def process_health(request: Request):
|
|
|
1593
1740
|
processes["worker_pool"] = {"status": worker_status}
|
|
1594
1741
|
|
|
1595
1742
|
# Memory usage of current process (approximate)
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
import resource
|
|
1599
|
-
usage = resource.getrusage(resource.RUSAGE_SELF)
|
|
1600
|
-
memory_mb = round(usage.ru_maxrss / (1024 * 1024), 1)
|
|
1601
|
-
except Exception:
|
|
1602
|
-
pass
|
|
1743
|
+
from superlocalmemory.core.platform_utils import get_rss_mb
|
|
1744
|
+
memory_mb = round(get_rss_mb(), 1)
|
|
1603
1745
|
|
|
1604
1746
|
return {
|
|
1605
1747
|
"processes": processes,
|
|
@@ -1007,8 +1007,53 @@
|
|
|
1007
1007
|
</div>
|
|
1008
1008
|
</div>
|
|
1009
1009
|
|
|
1010
|
+
<!-- Step 3: Embedding Configuration (V3.4.24) -->
|
|
1011
|
+
<div class="mt-3 pt-3 border-top" id="settings-embedding-panel">
|
|
1012
|
+
<h6 class="text-muted"><i class="bi bi-cpu"></i> Step 3: Embedding Model</h6>
|
|
1013
|
+
<p class="small text-muted mb-2">
|
|
1014
|
+
Controls how text is converted to vectors for semantic search.
|
|
1015
|
+
Default: local model (768d). Custom: any OpenAI-compatible endpoint.
|
|
1016
|
+
</p>
|
|
1017
|
+
<div class="row g-2 mb-2">
|
|
1018
|
+
<div class="col-md-4">
|
|
1019
|
+
<label class="form-label small">Embedding Provider</label>
|
|
1020
|
+
<select class="form-select form-select-sm" id="settings-emb-provider">
|
|
1021
|
+
<option value="default">Default (Local Model)</option>
|
|
1022
|
+
<option value="openai">Custom Endpoint (OpenAI-compatible)</option>
|
|
1023
|
+
</select>
|
|
1024
|
+
</div>
|
|
1025
|
+
<div class="col-md-4" id="settings-emb-model-col" style="display:none;">
|
|
1026
|
+
<label class="form-label small">Model Name</label>
|
|
1027
|
+
<input type="text" id="settings-emb-model" class="form-control form-control-sm" placeholder="e.g. Qwen3-Embedding">
|
|
1028
|
+
</div>
|
|
1029
|
+
<div class="col-md-4" id="settings-emb-dim-col" style="display:none;">
|
|
1030
|
+
<label class="form-label small">Dimension</label>
|
|
1031
|
+
<input type="number" id="settings-emb-dimension" class="form-control form-control-sm" placeholder="e.g. 1024" min="64" max="8192">
|
|
1032
|
+
</div>
|
|
1033
|
+
</div>
|
|
1034
|
+
<div class="row g-2 mb-2" id="settings-emb-endpoint-row" style="display:none;">
|
|
1035
|
+
<div class="col-md-8">
|
|
1036
|
+
<label class="form-label small">Embedding Endpoint</label>
|
|
1037
|
+
<input type="text" id="settings-emb-endpoint" class="form-control form-control-sm" placeholder="http://localhost:8045/v1/embeddings">
|
|
1038
|
+
</div>
|
|
1039
|
+
<div class="col-md-4">
|
|
1040
|
+
<label class="form-label small">API Key (optional)</label>
|
|
1041
|
+
<input type="password" id="settings-emb-key" class="form-control form-control-sm" placeholder="not-needed">
|
|
1042
|
+
</div>
|
|
1043
|
+
</div>
|
|
1044
|
+
<div id="settings-emb-test-row" style="display:none;">
|
|
1045
|
+
<button class="btn btn-sm btn-outline-info" id="settings-emb-test-btn">
|
|
1046
|
+
<i class="bi bi-lightning"></i> Test Embedding
|
|
1047
|
+
</button>
|
|
1048
|
+
<span id="settings-emb-test-result" class="ms-2 small"></span>
|
|
1049
|
+
</div>
|
|
1050
|
+
<div id="settings-emb-info" class="small text-muted mt-1">
|
|
1051
|
+
Using local <strong>nomic-embed-text-v1.5</strong> (768d)
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
|
|
1010
1055
|
<!-- Save button -->
|
|
1011
|
-
<div class="mt-
|
|
1056
|
+
<div class="mt-3">
|
|
1012
1057
|
<button class="btn btn-primary" id="settings-save-all">
|
|
1013
1058
|
<i class="bi bi-check-circle"></i> Save Configuration
|
|
1014
1059
|
</button>
|
|
@@ -353,20 +353,28 @@ async function saveAllSettings() {
|
|
|
353
353
|
if (statusEl) { statusEl.textContent = 'Saving...'; statusEl.style.display = 'inline'; statusEl.className = 'ms-2 text-muted'; }
|
|
354
354
|
|
|
355
355
|
try {
|
|
356
|
-
//
|
|
356
|
+
// V3.4.24: Include embedding params in save payload
|
|
357
|
+
var embParams = getEmbeddingParams();
|
|
358
|
+
var payload = Object.assign({mode: mode, provider: provider, model: model, api_key: apiKey}, embParams);
|
|
357
359
|
var modeResp = await fetch('/api/v3/mode/set', {
|
|
358
360
|
method: 'POST',
|
|
359
361
|
headers: {'Content-Type': 'application/json'},
|
|
360
|
-
body: JSON.stringify(
|
|
362
|
+
body: JSON.stringify(payload)
|
|
361
363
|
});
|
|
362
364
|
|
|
363
365
|
if (modeResp.ok) {
|
|
366
|
+
var modeData = await modeResp.json();
|
|
367
|
+
var msg = 'Configuration saved! Mode: ' + mode.toUpperCase() +
|
|
368
|
+
(provider !== 'none' ? ' | Provider: ' + provider : '');
|
|
369
|
+
if (modeData.needs_reindex) {
|
|
370
|
+
msg += ' | Embeddings will be re-indexed on next use (may take several minutes).';
|
|
371
|
+
}
|
|
364
372
|
if (statusEl) {
|
|
365
|
-
statusEl.textContent =
|
|
366
|
-
|
|
367
|
-
statusEl.className = 'ms-2 text-success fw-bold';
|
|
373
|
+
statusEl.textContent = msg;
|
|
374
|
+
statusEl.className = modeData.needs_reindex ? 'ms-2 text-warning fw-bold' : 'ms-2 text-success fw-bold';
|
|
368
375
|
}
|
|
369
376
|
loadModeSettings();
|
|
377
|
+
loadEmbeddingSettings();
|
|
370
378
|
} else {
|
|
371
379
|
if (statusEl) { statusEl.textContent = 'Save failed'; statusEl.className = 'ms-2 text-danger'; }
|
|
372
380
|
}
|
|
@@ -381,10 +389,127 @@ async function saveAllSettings() {
|
|
|
381
389
|
}, 5000);
|
|
382
390
|
}
|
|
383
391
|
|
|
392
|
+
// ============================================================================
|
|
393
|
+
// Embedding Configuration (V3.4.24 — Custom OpenAI-compatible endpoints)
|
|
394
|
+
// ============================================================================
|
|
395
|
+
|
|
396
|
+
async function loadEmbeddingSettings() {
|
|
397
|
+
try {
|
|
398
|
+
var resp = await fetch('/api/v3/embedding/config');
|
|
399
|
+
if (!resp.ok) return;
|
|
400
|
+
var data = await resp.json();
|
|
401
|
+
|
|
402
|
+
var provEl = document.getElementById('settings-emb-provider');
|
|
403
|
+
if (provEl) {
|
|
404
|
+
provEl.value = data.is_openai_compatible ? 'openai' : 'default';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (data.is_openai_compatible) {
|
|
408
|
+
var modelEl = document.getElementById('settings-emb-model');
|
|
409
|
+
if (modelEl) modelEl.value = data.model_name || '';
|
|
410
|
+
var dimEl = document.getElementById('settings-emb-dimension');
|
|
411
|
+
if (dimEl) dimEl.value = data.dimension || '';
|
|
412
|
+
var epEl = document.getElementById('settings-emb-endpoint');
|
|
413
|
+
if (epEl) epEl.value = data.api_endpoint || '';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
updateEmbeddingUI();
|
|
417
|
+
|
|
418
|
+
var info = document.getElementById('settings-emb-info');
|
|
419
|
+
if (info) {
|
|
420
|
+
var _name = (data.model_name || 'unknown').replace(/[<>&"']/g, function(c) {
|
|
421
|
+
return {'<':'<','>':'>','&':'&','"':'"',"'":'''}[c];
|
|
422
|
+
});
|
|
423
|
+
if (data.is_openai_compatible) {
|
|
424
|
+
info.innerHTML = 'Using custom endpoint: <strong>' + _name + '</strong> (' + data.dimension + 'd)';
|
|
425
|
+
} else {
|
|
426
|
+
info.innerHTML = 'Using local <strong>' + _name + '</strong> (' + data.dimension + 'd)';
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} catch (e) {
|
|
430
|
+
console.log('Load embedding settings error:', e);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function updateEmbeddingUI() {
|
|
435
|
+
var provider = document.getElementById('settings-emb-provider')?.value || 'default';
|
|
436
|
+
var isCustom = provider === 'openai';
|
|
437
|
+
|
|
438
|
+
var modelCol = document.getElementById('settings-emb-model-col');
|
|
439
|
+
var dimCol = document.getElementById('settings-emb-dim-col');
|
|
440
|
+
var endpointRow = document.getElementById('settings-emb-endpoint-row');
|
|
441
|
+
var testRow = document.getElementById('settings-emb-test-row');
|
|
442
|
+
|
|
443
|
+
if (modelCol) modelCol.style.display = isCustom ? 'block' : 'none';
|
|
444
|
+
if (dimCol) dimCol.style.display = isCustom ? 'block' : 'none';
|
|
445
|
+
if (endpointRow) endpointRow.style.display = isCustom ? 'flex' : 'none';
|
|
446
|
+
if (testRow) testRow.style.display = isCustom ? 'block' : 'none';
|
|
447
|
+
|
|
448
|
+
var info = document.getElementById('settings-emb-info');
|
|
449
|
+
if (info && !isCustom) {
|
|
450
|
+
info.innerHTML = 'Using local <strong>nomic-embed-text-v1.5</strong> (768d)';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function testEmbeddingEndpoint() {
|
|
455
|
+
var endpoint = document.getElementById('settings-emb-endpoint')?.value || '';
|
|
456
|
+
var model = document.getElementById('settings-emb-model')?.value || '';
|
|
457
|
+
var key = document.getElementById('settings-emb-key')?.value || '';
|
|
458
|
+
var resultEl = document.getElementById('settings-emb-test-result');
|
|
459
|
+
|
|
460
|
+
if (!endpoint) {
|
|
461
|
+
if (resultEl) { resultEl.textContent = 'Enter an endpoint first'; resultEl.className = 'ms-2 small text-danger'; }
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (resultEl) { resultEl.textContent = 'Testing...'; resultEl.className = 'ms-2 small text-muted'; }
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
var resp = await fetch('/api/v3/embedding/test', {
|
|
469
|
+
method: 'POST',
|
|
470
|
+
headers: {'Content-Type': 'application/json'},
|
|
471
|
+
body: JSON.stringify({api_endpoint: endpoint, model_name: model, api_key: key})
|
|
472
|
+
});
|
|
473
|
+
var data = await resp.json();
|
|
474
|
+
if (data.success) {
|
|
475
|
+
if (resultEl) { resultEl.textContent = data.message; resultEl.className = 'ms-2 small text-success fw-bold'; }
|
|
476
|
+
var dimEl = document.getElementById('settings-emb-dimension');
|
|
477
|
+
if (dimEl && data.dimension) {
|
|
478
|
+
if (!dimEl.value) {
|
|
479
|
+
dimEl.value = data.dimension;
|
|
480
|
+
} else if (parseInt(dimEl.value) !== data.dimension) {
|
|
481
|
+
if (resultEl) {
|
|
482
|
+
resultEl.textContent = 'Connected! Warning: endpoint returns ' + data.dimension + 'd but you entered ' + dimEl.value + 'd';
|
|
483
|
+
resultEl.className = 'ms-2 small text-warning fw-bold';
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
if (resultEl) { resultEl.textContent = 'Failed: ' + (data.error || 'Unknown'); resultEl.className = 'ms-2 small text-danger'; }
|
|
489
|
+
}
|
|
490
|
+
} catch (e) {
|
|
491
|
+
if (resultEl) { resultEl.textContent = 'Error: ' + e.message; resultEl.className = 'ms-2 small text-danger'; }
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function getEmbeddingParams() {
|
|
496
|
+
var provider = document.getElementById('settings-emb-provider')?.value || 'default';
|
|
497
|
+
if (provider !== 'openai') return {};
|
|
498
|
+
return {
|
|
499
|
+
embedding_provider: 'openai',
|
|
500
|
+
embedding_endpoint: document.getElementById('settings-emb-endpoint')?.value || '',
|
|
501
|
+
embedding_model: document.getElementById('settings-emb-model')?.value || '',
|
|
502
|
+
embedding_dimension: parseInt(document.getElementById('settings-emb-dimension')?.value) || 0,
|
|
503
|
+
embedding_key: document.getElementById('settings-emb-key')?.value || '',
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
384
507
|
// Bind events
|
|
385
508
|
document.getElementById('settings-provider')?.addEventListener('change', updateProviderUI);
|
|
386
509
|
document.getElementById('settings-save-all')?.addEventListener('click', saveAllSettings);
|
|
387
510
|
document.getElementById('settings-test-btn')?.addEventListener('click', testConnection);
|
|
511
|
+
document.getElementById('settings-emb-provider')?.addEventListener('change', updateEmbeddingUI);
|
|
512
|
+
document.getElementById('settings-emb-test-btn')?.addEventListener('click', testEmbeddingEndpoint);
|
|
388
513
|
|
|
389
514
|
// Mode radio buttons
|
|
390
515
|
document.querySelectorAll('input[name="settings-mode-radio"]').forEach(function(radio) {
|
|
@@ -395,5 +520,6 @@ document.querySelectorAll('input[name="settings-mode-radio"]').forEach(function(
|
|
|
395
520
|
document.getElementById('settings-tab')?.addEventListener('shown.bs.tab', function() {
|
|
396
521
|
loadAutoSettings();
|
|
397
522
|
loadModeSettings();
|
|
523
|
+
loadEmbeddingSettings();
|
|
398
524
|
updateModeUI();
|
|
399
525
|
});
|