opencode-mem 2.11.3 → 2.11.6
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/README.md +19 -1
- package/dist/services/sqlite/hnsw-index.d.ts +1 -1
- package/dist/services/sqlite/hnsw-index.d.ts.map +1 -1
- package/dist/services/sqlite/hnsw-index.js +40 -36
- package/dist/services/sqlite/vector-search.d.ts.map +1 -1
- package/dist/services/sqlite/vector-search.js +5 -0
- package/dist/services/web-server.d.ts.map +1 -1
- package/dist/services/web-server.js +3 -0
- package/dist/web/app.js +98 -105
- package/dist/web/i18n.d.ts +2 -0
- package/dist/web/i18n.d.ts.map +1 -0
- package/dist/web/i18n.js +265 -0
- package/dist/web/index.html +76 -50
- package/dist/web/styles.css +17 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,6 +22,24 @@ A persistent memory system for AI coding agents that enables long-term context r
|
|
|
22
22
|
|
|
23
23
|
Local vector database with SQLite + HNSW (hnswlib-wasm), persistent project memories, automatic user profile learning, unified memory-prompt timeline, full-featured web UI, intelligent prompt-based memory extraction, multi-provider AI support (OpenAI, Anthropic), 12+ local embedding models, smart deduplication, and built-in privacy protection.
|
|
24
24
|
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
This plugin uses `hnswlib-node` for fast vector similarity search, which requires native compilation. Ensure you have:
|
|
28
|
+
|
|
29
|
+
**All platforms:**
|
|
30
|
+
|
|
31
|
+
- Python 3.x
|
|
32
|
+
- A C++ compiler (gcc, clang, or MSVC)
|
|
33
|
+
- `make` or CMake
|
|
34
|
+
|
|
35
|
+
**Platform-specific setup:**
|
|
36
|
+
|
|
37
|
+
| Platform | Requirements |
|
|
38
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| **macOS** | Xcode Command Line Tools: `xcode-select --install` |
|
|
40
|
+
| **Linux** | Build essentials: `sudo apt install build-essential python3` (Debian/Ubuntu) or `sudo pacman -S base-devel python` (Arch) |
|
|
41
|
+
| **Windows** | Visual Studio Build Tools with C++ workload, or Windows Build Tools: `npm install -g windows-build-tools` |
|
|
42
|
+
|
|
25
43
|
## Getting Started
|
|
26
44
|
|
|
27
45
|
Add to your OpenCode configuration at `~/.config/opencode/opencode.json`:
|
|
@@ -32,7 +50,7 @@ Add to your OpenCode configuration at `~/.config/opencode/opencode.json`:
|
|
|
32
50
|
}
|
|
33
51
|
```
|
|
34
52
|
|
|
35
|
-
The plugin downloads automatically on next startup.
|
|
53
|
+
The plugin downloads automatically on next startup.
|
|
36
54
|
|
|
37
55
|
## Usage Examples
|
|
38
56
|
|
|
@@ -13,7 +13,6 @@ export declare class HNSWIndex {
|
|
|
13
13
|
private initialized;
|
|
14
14
|
constructor(dimensions: number, indexPath: string);
|
|
15
15
|
private ensureInitialized;
|
|
16
|
-
private vectorToArray;
|
|
17
16
|
insert(id: string, vector: Float32Array): Promise<void>;
|
|
18
17
|
insertBatch(items: HNSWIndexData[]): Promise<void>;
|
|
19
18
|
search(queryVector: Float32Array, k: number): Promise<{
|
|
@@ -23,6 +22,7 @@ export declare class HNSWIndex {
|
|
|
23
22
|
delete(id: string): Promise<void>;
|
|
24
23
|
save(): Promise<void>;
|
|
25
24
|
getCount(): number;
|
|
25
|
+
isPopulated(): boolean;
|
|
26
26
|
}
|
|
27
27
|
export declare class HNSWIndexManager {
|
|
28
28
|
private indexes;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hnsw-index.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/hnsw-index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hnsw-index.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/hnsw-index.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;YAKnC,iBAAiB;IAyBzB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBvD,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBlD,MAAM,CAAC,WAAW,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAqBzF,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,QAAQ,IAAI,MAAM;IAIlB,WAAW,IAAI,OAAO;CAGvB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAO3B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IAWzE,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS;IAWvE,gBAAgB,CACpB,EAAE,EAAE,GAAG,EACP,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAoCV,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhF,sBAAsB,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CA0BpE"}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import { mkdirSync, existsSync, writeFileSync,
|
|
1
|
+
import { mkdirSync, existsSync, writeFileSync, unlinkSync, readdirSync } from "node:fs";
|
|
2
2
|
import { join, dirname, basename } from "node:path";
|
|
3
3
|
import { log } from "../logger.js";
|
|
4
4
|
import { CONFIG } from "../../config.js";
|
|
5
|
-
|
|
5
|
+
let HNSWLib = null;
|
|
6
|
+
async function loadHNSWLib() {
|
|
7
|
+
if (!HNSWLib) {
|
|
8
|
+
// hnswlib-wasm is compiled with Emscripten -sENVIRONMENT=web and requires
|
|
9
|
+
// a browser-like global. This monkey-patch allows it to load in Node.js/Bun.
|
|
10
|
+
if (typeof globalThis.window === "undefined") {
|
|
11
|
+
globalThis.window = globalThis;
|
|
12
|
+
}
|
|
13
|
+
const { loadHnswlib } = await import("hnswlib-wasm");
|
|
14
|
+
HNSWLib = await loadHnswlib();
|
|
15
|
+
}
|
|
16
|
+
return HNSWLib;
|
|
17
|
+
}
|
|
6
18
|
export class HNSWIndex {
|
|
7
19
|
index = null;
|
|
8
20
|
idMap = new Map();
|
|
@@ -19,41 +31,23 @@ export class HNSWIndex {
|
|
|
19
31
|
async ensureInitialized() {
|
|
20
32
|
if (this.initialized)
|
|
21
33
|
return;
|
|
34
|
+
const hnsw = await loadHNSWLib();
|
|
22
35
|
const dir = dirname(this.indexPath);
|
|
23
36
|
if (!existsSync(dir)) {
|
|
24
37
|
mkdirSync(dir, { recursive: true });
|
|
25
38
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.nextId = meta.nextId || 0;
|
|
34
|
-
this.idMap = new Map(Object.entries(meta.idMap || {}).map(([k, v]) => [Number(k), v]));
|
|
35
|
-
this.reverseMap = new Map(Object.entries(meta.reverseMap || {}));
|
|
36
|
-
}
|
|
37
|
-
log("HNSW index loaded", { path: this.indexPath, count: this.nextId });
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
log("Failed to load HNSW index, creating new", {
|
|
41
|
-
path: this.indexPath,
|
|
42
|
-
error: String(error),
|
|
43
|
-
});
|
|
44
|
-
this.index = new HierarchicalNSW("cosine", this.dimensions);
|
|
45
|
-
await this.index.initIndex(this.maxElements);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
this.index = new HierarchicalNSW("cosine", this.dimensions);
|
|
50
|
-
await this.index.initIndex(this.maxElements);
|
|
51
|
-
log("HNSW index created", { path: this.indexPath, dimensions: this.dimensions });
|
|
52
|
-
}
|
|
39
|
+
// hnswlib-wasm uses Emscripten MEMFS (in-memory virtual FS) and has no
|
|
40
|
+
// exported FS API for bridging to real filesystem. HNSW indexes are kept
|
|
41
|
+
// purely in-memory and rebuilt from SQLite vectors on process restart.
|
|
42
|
+
// Constructor requires 3 args: (spaceName, numDimensions, autoSaveFilename)
|
|
43
|
+
this.index = new hnsw.HierarchicalNSW("cosine", this.dimensions, "");
|
|
44
|
+
// initIndex requires 4 args: (maxElements, m, efConstruction, randomSeed)
|
|
45
|
+
this.index.initIndex(this.maxElements, 16, 200, 100);
|
|
53
46
|
this.initialized = true;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
log("HNSW index initialized (in-memory)", {
|
|
48
|
+
path: this.indexPath,
|
|
49
|
+
dimensions: this.dimensions,
|
|
50
|
+
});
|
|
57
51
|
}
|
|
58
52
|
async insert(id, vector) {
|
|
59
53
|
await this.ensureInitialized();
|
|
@@ -62,7 +56,8 @@ export class HNSWIndex {
|
|
|
62
56
|
this.index.markDelete(internalId);
|
|
63
57
|
}
|
|
64
58
|
const internalId = this.nextId++;
|
|
65
|
-
|
|
59
|
+
// hnswlib-wasm addPoint requires 3 args: (point, label, replaceDeleted)
|
|
60
|
+
this.index.addPoint(vector, internalId, false);
|
|
66
61
|
this.idMap.set(internalId, id);
|
|
67
62
|
this.reverseMap.set(id, internalId);
|
|
68
63
|
await this.save();
|
|
@@ -75,7 +70,8 @@ export class HNSWIndex {
|
|
|
75
70
|
this.index.markDelete(internalId);
|
|
76
71
|
}
|
|
77
72
|
const internalId = this.nextId++;
|
|
78
|
-
|
|
73
|
+
// hnswlib-wasm addPoint requires 3 args: (point, label, replaceDeleted)
|
|
74
|
+
this.index.addPoint(item.vector, internalId, false);
|
|
79
75
|
this.idMap.set(internalId, item.id);
|
|
80
76
|
this.reverseMap.set(item.id, internalId);
|
|
81
77
|
}
|
|
@@ -84,7 +80,11 @@ export class HNSWIndex {
|
|
|
84
80
|
async search(queryVector, k) {
|
|
85
81
|
await this.ensureInitialized();
|
|
86
82
|
try {
|
|
87
|
-
|
|
83
|
+
// hnswlib-wasm searchKnn requires 3 args: (queryPoint, numNeighbors, filter)
|
|
84
|
+
const actualK = Math.min(k, this.reverseMap.size);
|
|
85
|
+
if (actualK === 0)
|
|
86
|
+
return [];
|
|
87
|
+
const results = this.index.searchKnn(queryVector, actualK, null);
|
|
88
88
|
return results.neighbors
|
|
89
89
|
.map((internalId, idx) => ({
|
|
90
90
|
id: this.idMap.get(internalId) || "",
|
|
@@ -114,7 +114,8 @@ export class HNSWIndex {
|
|
|
114
114
|
if (!existsSync(dir)) {
|
|
115
115
|
mkdirSync(dir, { recursive: true });
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
// Only persist id mapping (.meta file). HNSW index data lives in-memory
|
|
118
|
+
// and is rebuilt from SQLite vectors on process restart.
|
|
118
119
|
const metaPath = this.indexPath + ".meta";
|
|
119
120
|
const meta = {
|
|
120
121
|
nextId: this.nextId,
|
|
@@ -126,6 +127,9 @@ export class HNSWIndex {
|
|
|
126
127
|
getCount() {
|
|
127
128
|
return this.reverseMap.size;
|
|
128
129
|
}
|
|
130
|
+
isPopulated() {
|
|
131
|
+
return this.reverseMap.size > 0;
|
|
132
|
+
}
|
|
129
133
|
}
|
|
130
134
|
export class HNSWIndexManager {
|
|
131
135
|
indexes = new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAI9C,qBAAa,YAAY;IACvB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI;IAqDvE,aAAa,CACjB,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExE,QAAA,MAAM,QAAQ,sCAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAI9C,qBAAa,YAAY;IACvB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI;IAqDvE,aAAa,CACjB,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;IAmFpB,kBAAkB,CACtB,MAAM,EAAE,SAAS,EAAE,EACnB,WAAW,EAAE,YAAY,EACzB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,mBAAmB,EAAE,MAAM,EAC3B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,EAAE,CAAC;IAiBpB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlF,YAAY,CAChB,EAAE,EAAE,YAAY,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,KAAK,CAAC,EAAE,SAAS,EACjB,UAAU,CAAC,EAAE,YAAY,GACxB,OAAO,CAAC,IAAI,CAAC;IA4BhB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAW1E,cAAc,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAKvC,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI;IAK7D,sBAAsB,CAAC,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE;IAgBlE,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM;IAM5D,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,MAAM;IAMzC,eAAe,CAAC,EAAE,EAAE,YAAY,GAAG,GAAG,EAAE;IAexC,SAAS,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKnD,WAAW,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK/C,gBAAgB,CACpB,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAIhB,eAAe,IAAI,gBAAgB;CAGpC;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -33,6 +33,11 @@ export class VectorSearch {
|
|
|
33
33
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
34
34
|
const contentIndex = hnswIndexManager.getIndex(shard.scope, shard.scopeHash, shard.shardIndex);
|
|
35
35
|
const tagsIndex = hnswIndexManager.getTagsIndex(shard.scope, shard.scopeHash, shard.shardIndex);
|
|
36
|
+
// HNSW indexes are in-memory only (hnswlib-wasm has no real FS bridging).
|
|
37
|
+
// Auto-rebuild from SQLite vectors if indexes are empty after process restart.
|
|
38
|
+
if (!contentIndex.isPopulated()) {
|
|
39
|
+
await hnswIndexManager.rebuildFromShard(db, shard.scope, shard.scopeHash, shard.shardIndex);
|
|
40
|
+
}
|
|
36
41
|
const contentResults = await contentIndex.search(queryVector, limit * 4);
|
|
37
42
|
const tagsResults = await tagsIndex.search(queryVector, limit * 4);
|
|
38
43
|
const scoreMap = new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"AAiCA,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,mBAAmB,CAA+B;IAC1D,OAAO,CAAC,kBAAkB,CAAsC;gBAEpD,MAAM,EAAE,eAAe;IAInC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,MAAM;IAgCpB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,mBAAmB;YAOb,eAAe;IA+BvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,SAAS,IAAI,OAAO;IAIpB,aAAa,IAAI,OAAO;IAIxB,MAAM,IAAI,MAAM;IAIV,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;YAchC,aAAa;
|
|
1
|
+
{"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"AAiCA,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,mBAAmB,CAA+B;IAC1D,OAAO,CAAC,kBAAkB,CAAsC;gBAEpD,MAAM,EAAE,eAAe;IAInC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,MAAM;IAgCpB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,mBAAmB;YAOb,eAAe;IA+BvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,SAAS,IAAI,OAAO;IAIpB,aAAa,IAAI,OAAO;IAIxB,MAAM,IAAI,MAAM;IAIV,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;YAchC,aAAa;IAuN3B,OAAO,CAAC,eAAe;IA4BvB,OAAO,CAAC,YAAY;CAWrB;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAIhF"}
|
|
@@ -145,6 +145,9 @@ export class WebServer {
|
|
|
145
145
|
if (path === "/app.js") {
|
|
146
146
|
return this.serveStaticFile("app.js", "application/javascript");
|
|
147
147
|
}
|
|
148
|
+
if (path === "/i18n.js") {
|
|
149
|
+
return this.serveStaticFile("i18n.js", "application/javascript");
|
|
150
|
+
}
|
|
148
151
|
if (path === "/favicon.ico") {
|
|
149
152
|
return this.serveStaticFile("favicon.ico", "image/x-icon");
|
|
150
153
|
}
|