@lotargo/memory_plugin 1.5.3 → 1.6.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.
@@ -115,6 +115,47 @@ export async function handleEngineAction(value, config) {
115
115
  }
116
116
  break;
117
117
  }
118
+ case "vector_dim": {
119
+ const dimItems = [
120
+ { label: "0 - Auto (Detect from Model)", value: 0, info: "Use the vector dimension produced by the embedding model (Recommended)" },
121
+ { label: "384", value: 384, info: "MiniLM-L6 / multilingual-e5-small" },
122
+ { label: "512", value: 512, info: "Compact embedding models" },
123
+ { label: "768", value: 768, info: "e5-large / bge-base / MiniLM-L12" },
124
+ { label: "1024", value: 1024, info: "bge-m3 / modern multilingual models" },
125
+ { label: "1536", value: 1536, info: "OpenAI text-embedding-3-small (for compatible local models)" },
126
+ { label: "3072", value: 3072, info: "OpenAI text-embedding-3-large (for compatible local models)" },
127
+ { label: "Custom Dimension...", value: "custom", info: "Specify any dimension not listed here" },
128
+ ];
129
+ const currentDim = config.vectorDimension || 0;
130
+ const initialDimIdx = Math.max(0, dimItems.findIndex((i) => i.value === currentDim));
131
+
132
+ const subRes = await selectSimpleMenu({
133
+ title: "SELECT VECTOR DIMENSION",
134
+ subtitle: "Force a fixed embedding size (pads/truncates model output for consistent matching)",
135
+ items: dimItems,
136
+ initialIndex: initialDimIdx,
137
+ });
138
+
139
+ if (subRes.action === "select") {
140
+ let chosenDim = subRes.value;
141
+ if (subRes.value === "custom") {
142
+ const inputRes = await readTextInput("Enter Vector Dimension (positive integer)", "768");
143
+ if (inputRes.action === "submit" && inputRes.value) {
144
+ const parsed = Number.parseInt(inputRes.value, 10);
145
+ if (!Number.isInteger(parsed) || parsed <= 0) {
146
+ console.log(`\x1b[31mInvalid dimension: "${inputRes.value}". Expected a positive integer.\x1b[0m`);
147
+ await waitForEnter();
148
+ break;
149
+ }
150
+ chosenDim = parsed;
151
+ } else {
152
+ break;
153
+ }
154
+ }
155
+ updateConfig({ vectorDimension: chosenDim });
156
+ }
157
+ break;
158
+ }
118
159
  case "batch_size": {
119
160
  const batchItems = [
120
161
  { label: "Batch Size 1 (Single Item)", value: 1, info: "Process micro-chunks strictly 1 by 1" },
@@ -34,6 +34,7 @@ import {
34
34
  readTextInput,
35
35
  promptText,
36
36
  waitForEnter,
37
+ downloadModelWithProgress,
37
38
  } from "../ui.js";
38
39
 
39
40
  export async function handleStorageAction(value, config, stats) {
@@ -556,6 +557,63 @@ export async function handleStorageAction(value, config, stats) {
556
557
  }
557
558
  break;
558
559
  }
560
+ case "reindex_embeddings": {
561
+ const db = await getDatabase();
562
+ const chunkCountRow = await db.prepare("SELECT COUNT(*) as cnt FROM micro_chunks").get();
563
+ const chunkCount = chunkCountRow ? chunkCountRow.cnt : 0;
564
+ const docCountRow = await db.prepare("SELECT COUNT(*) as cnt FROM documents").get();
565
+ const docCount = docCountRow ? docCountRow.cnt : 0;
566
+
567
+ if (chunkCount === 0) {
568
+ console.clear();
569
+ console.log("\n [*] No micro-chunks to re-index. Ingest documents first.\n");
570
+ await waitForEnter();
571
+ break;
572
+ }
573
+
574
+ const dimLabel = config.vectorDimension > 0 ? `${config.vectorDimension}` : "AUTO";
575
+ console.clear();
576
+ console.log(`\n \x1b[1m\x1b[37mRE-EMBED KNOWLEDGE BASE\x1b[0m\n`);
577
+ console.log(` Model: ${config.embeddingModel}`);
578
+ console.log(` Dimension: ${dimLabel}`);
579
+ console.log(` Documents: ${docCount}`);
580
+ console.log(` Chunks: ${chunkCount}`);
581
+ console.log("\n Re-embeds ALL stored vectors with the active model & dimension.");
582
+ console.log(" Facts, fact-doc links, FTS index & graph edges are preserved.\n");
583
+
584
+ const confirmRes = await selectSimpleMenu({
585
+ title: "CONFIRM RE-INDEX",
586
+ items: [
587
+ {
588
+ label: "[CONFIRM] Re-Embed All Documents",
589
+ value: "confirm",
590
+ info: "Loads/downloads the model if needed, then re-embeds every chunk with the active configuration",
591
+ },
592
+ { label: "< Cancel / Back", value: "cancel" },
593
+ ],
594
+ });
595
+ if (!(confirmRes.action === "select" && confirmRes.value === "confirm")) break;
596
+
597
+ try {
598
+ const { reindexEmbeddings } = await import("../../ingest/pipeline.js");
599
+ await downloadModelWithProgress(config.embeddingModel, "embedding");
600
+ console.log(`\n [REINDEX] Re-embedding ${chunkCount} chunks...\n`);
601
+ const res = await reindexEmbeddings({
602
+ progressCallback: (p) => {
603
+ process.stdout.write(`\r Progress: ${p.done}/${p.total} chunks`);
604
+ },
605
+ });
606
+ process.stdout.write("\n");
607
+ console.log(`\n \x1b[32m[OK] Re-index complete!\x1b[0m`);
608
+ console.log(` Chunks re-embedded: ${res.reindexed}`);
609
+ console.log(` Documents affected: ${res.documentsAffected}`);
610
+ console.log(` Model: ${res.model} | Dimension: ${res.dimension || "AUTO"}\n`);
611
+ } catch (err) {
612
+ console.error(` \x1b[31m[ERROR] Re-index failed: ${err.message}\x1b[0m\n`);
613
+ }
614
+ await waitForEnter();
615
+ break;
616
+ }
559
617
  case "export_snapshot": {
560
618
  const { exportSnapshot } = await import("../../admin/snapshot.js");
561
619
  const defaultPath = join(MEMORY_DIR, "exports", `rag_snapshot_${Date.now()}.json.gz`);
@@ -0,0 +1,44 @@
1
+ import { createInterface } from "node:readline";
2
+
3
+ // Reading a token from argv leaks it into `ps`, Task Manager, shell history and
4
+ // CI logs. Preferred order: environment variable -> stdin prompt -> argv (warned).
5
+ export async function resolveSecret({ argvValue, envKeys = [], promptLabel = "Token", interactive = true }) {
6
+ for (const key of envKeys) {
7
+ const v = process.env[key];
8
+ if (v && String(v).trim()) return String(v).trim();
9
+ }
10
+
11
+ if (argvValue && String(argvValue).trim()) {
12
+ console.error(
13
+ ` [WARN] Passing a secret on the command line exposes it to the process list and shell history. ` +
14
+ `Prefer ${envKeys[0] || "an environment variable"} or the interactive prompt.`
15
+ );
16
+ return String(argvValue).trim();
17
+ }
18
+
19
+ if (!interactive || !process.stdin.isTTY) return null;
20
+ return await readHiddenLine(`${promptLabel}: `);
21
+ }
22
+
23
+ // Read a line from stdin without echoing it back to the terminal.
24
+ export function readHiddenLine(prompt) {
25
+ return new Promise((resolve) => {
26
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
27
+ const onData = (char) => {
28
+ const s = String(char);
29
+ if (s === "\n" || s === "\r" || s === "\u0004") {
30
+ process.stdin.removeListener("data", onData);
31
+ return;
32
+ }
33
+ process.stdout.write("\x1b[2K\x1b[200D" + prompt + "*".repeat(rl.line.length));
34
+ };
35
+ process.stdout.write(prompt);
36
+ process.stdin.on("data", onData);
37
+ rl.question("", (answer) => {
38
+ process.stdin.removeListener("data", onData);
39
+ rl.close();
40
+ process.stdout.write("\n");
41
+ resolve(String(answer || "").trim());
42
+ });
43
+ });
44
+ }