opencode-mem 2.14.3 → 2.15.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/dist/services/api-handlers.d.ts.map +1 -1
- package/dist/services/api-handlers.js +55 -25
- package/dist/services/client.d.ts.map +1 -1
- package/dist/services/client.js +27 -1
- package/dist/services/embedding.d.ts.map +1 -1
- package/dist/services/embedding.js +17 -0
- package/dist/services/sqlite/connection-manager.d.ts +1 -1
- package/dist/services/sqlite/connection-manager.d.ts.map +1 -1
- package/dist/services/sqlite/sqlite-bootstrap.d.ts +3 -1
- package/dist/services/sqlite/sqlite-bootstrap.d.ts.map +1 -1
- package/dist/services/sqlite/sqlite-bootstrap.js +60 -4
- package/dist/services/sqlite/vector-search.d.ts +1 -1
- package/dist/services/sqlite/vector-search.d.ts.map +1 -1
- package/dist/services/web-server.d.ts.map +1 -1
- package/dist/services/web-server.js +62 -1
- package/dist/web/app.js +6 -3
- package/dist/web/i18n.js +150 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,UAAU,WAAW,CAAC,CAAC,GAAG,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,OAAO;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,iBAAiB,CAAC,CAAC;IAC3B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../src/services/api-handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,6 +31,9 @@ function safeJSONParse(jsonString) {
|
|
|
31
31
|
return undefined;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
function toBlob(vector) {
|
|
35
|
+
return vector ? new Uint8Array(vector.buffer) : null;
|
|
36
|
+
}
|
|
34
37
|
function extractScopeFromTag(tag) {
|
|
35
38
|
const parts = tag.split("_");
|
|
36
39
|
if (parts.length >= 3) {
|
|
@@ -262,7 +265,30 @@ export async function handleAddMemory(data) {
|
|
|
262
265
|
metadata: JSON.stringify({ source: "api" }),
|
|
263
266
|
};
|
|
264
267
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
265
|
-
|
|
268
|
+
// Use transaction for atomic SQLite insert
|
|
269
|
+
const insertMemory = db.transaction(() => {
|
|
270
|
+
const insertStmt = db.prepare(`
|
|
271
|
+
INSERT INTO memories (
|
|
272
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
273
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
274
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
275
|
+
`);
|
|
276
|
+
insertStmt.run(record.id, record.content, toBlob(record.vector), toBlob(record.tagsVector), record.containerTag, record.tags || null, record.type || null, record.createdAt, record.updatedAt, record.metadata || null, record.displayName || null, record.userName || null, record.userEmail || null, record.projectPath || null, record.projectName || null, record.gitRepoUrl || null);
|
|
277
|
+
});
|
|
278
|
+
insertMemory();
|
|
279
|
+
// Vector index update (outside transaction — vector backend is async)
|
|
280
|
+
try {
|
|
281
|
+
const backend = await vectorSearch.getBackend();
|
|
282
|
+
await backend.insert({ id: record.id, vector: record.vector, shard, kind: "content" });
|
|
283
|
+
if (record.tagsVector) {
|
|
284
|
+
await backend.insert({ id: record.id, vector: record.tagsVector, shard, kind: "tags" });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
// Rollback SQLite insert on vector backend failure
|
|
289
|
+
db.prepare(`DELETE FROM memories WHERE id = ?`).run(record.id);
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
266
292
|
shardManager.incrementVectorCount(shard.id);
|
|
267
293
|
return { success: true, data: { id } };
|
|
268
294
|
}
|
|
@@ -323,6 +349,7 @@ export async function handleUpdateMemory(id, data) {
|
|
|
323
349
|
if (!id)
|
|
324
350
|
return { success: false, error: "id is required" };
|
|
325
351
|
await embeddingService.warmup();
|
|
352
|
+
// Find the existing memory first (read-only — no data modified yet)
|
|
326
353
|
const projectShards = shardManager.getAllShards("project", "");
|
|
327
354
|
let foundShard = null, existingMemory = null;
|
|
328
355
|
for (const shard of projectShards) {
|
|
@@ -336,36 +363,39 @@ export async function handleUpdateMemory(id, data) {
|
|
|
336
363
|
}
|
|
337
364
|
if (!foundShard || !existingMemory)
|
|
338
365
|
return { success: false, error: "Memory not found" };
|
|
339
|
-
|
|
340
|
-
await vectorSearch.deleteVector(db, id, foundShard);
|
|
341
|
-
shardManager.decrementVectorCount(foundShard.id);
|
|
366
|
+
// STEP 1: Generate new embeddings FIRST (safe — no data deleted yet)
|
|
342
367
|
const newContent = data.content || existingMemory.content;
|
|
343
|
-
const tags = data.tags ||
|
|
368
|
+
const tags = data.tags ||
|
|
369
|
+
(existingMemory.tags ? existingMemory.tags.split(",").map((t) => t.trim()) : []);
|
|
344
370
|
const vector = await embeddingService.embedWithTimeout(newContent);
|
|
345
371
|
let tagsVector = undefined;
|
|
346
372
|
if (tags.length > 0) {
|
|
347
373
|
tagsVector = await embeddingService.embedWithTimeout(tags.join(", "));
|
|
348
374
|
}
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
await
|
|
368
|
-
|
|
375
|
+
const db = connectionManager.getConnection(foundShard.dbPath);
|
|
376
|
+
// STEP 2: Wrap SQLite delete + insert in a transaction
|
|
377
|
+
const updateTransaction = db.transaction(() => {
|
|
378
|
+
// Delete old record
|
|
379
|
+
db.prepare(`DELETE FROM memories WHERE id = ?`).run(id);
|
|
380
|
+
// Insert updated record
|
|
381
|
+
const insertStmt = db.prepare(`
|
|
382
|
+
INSERT INTO memories (
|
|
383
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
384
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
385
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
386
|
+
`);
|
|
387
|
+
insertStmt.run(id, newContent, toBlob(vector), toBlob(tagsVector), existingMemory.container_tag, tags.length > 0 ? tags.join(",") : null, data.type || existingMemory.type, existingMemory.created_at, Date.now(), existingMemory.metadata, existingMemory.display_name, existingMemory.user_name, existingMemory.user_email, existingMemory.project_path, existingMemory.project_name, existingMemory.git_repo_url);
|
|
388
|
+
});
|
|
389
|
+
// Execute the SQLite transaction atomically
|
|
390
|
+
updateTransaction();
|
|
391
|
+
// STEP 3: Update vector index (outside transaction — vector backend is async/in-memory)
|
|
392
|
+
const backend = await vectorSearch.getBackend();
|
|
393
|
+
await backend.delete({ id, shard: foundShard, kind: "content" });
|
|
394
|
+
await backend.delete({ id, shard: foundShard, kind: "tags" });
|
|
395
|
+
await backend.insert({ id, vector, shard: foundShard, kind: "content" });
|
|
396
|
+
if (tagsVector) {
|
|
397
|
+
await backend.insert({ id, vector: tagsVector, shard: foundShard, kind: "tags" });
|
|
398
|
+
}
|
|
369
399
|
return { success: true };
|
|
370
400
|
}
|
|
371
401
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/services/client.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,cAAc,CAAC;AAyDrD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,aAAa,CAAkB;;YAIzB,UAAU;IAiBlB,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAIjC,SAAS,IAAI;QACX,WAAW,EAAE,OAAO,CAAC;QACrB,WAAW,EAAE,OAAO,CAAC;QACrB,KAAK,EAAE,OAAO,CAAC;KAChB;IAQD,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,GAAE,WAAuB;;;;;;;;;;;;;IA6BlF,SAAS,CACb,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,UAAU,CAAC;QAClB,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB;;;;;;;;;IAyGG,YAAY,CAAC,QAAQ,EAAE,MAAM;;;;;;;IA2B7B,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK,EAAE,KAAK,GAAE,WAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2D7E,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4C5F;AAED,eAAO,MAAM,YAAY,mBAA0B,CAAC"}
|
package/dist/services/client.js
CHANGED
|
@@ -30,6 +30,9 @@ function safeJSONParse(jsonString) {
|
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
function toBlob(vector) {
|
|
34
|
+
return vector ? new Uint8Array(vector.buffer) : null;
|
|
35
|
+
}
|
|
33
36
|
function extractScopeFromContainerTag(containerTag) {
|
|
34
37
|
const parts = containerTag.split("_");
|
|
35
38
|
if (parts.length >= 3) {
|
|
@@ -134,7 +137,30 @@ export class LocalMemoryClient {
|
|
|
134
137
|
metadata: Object.keys(dynamicMetadata).length > 0 ? JSON.stringify(dynamicMetadata) : undefined,
|
|
135
138
|
};
|
|
136
139
|
const db = connectionManager.getConnection(shard.dbPath);
|
|
137
|
-
|
|
140
|
+
// Use transaction for atomic SQLite insert
|
|
141
|
+
const insertMemory = db.transaction(() => {
|
|
142
|
+
const insertStmt = db.prepare(`
|
|
143
|
+
INSERT INTO memories (
|
|
144
|
+
id, content, vector, tags_vector, container_tag, tags, type, created_at, updated_at,
|
|
145
|
+
metadata, display_name, user_name, user_email, project_path, project_name, git_repo_url
|
|
146
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
147
|
+
`);
|
|
148
|
+
insertStmt.run(record.id, record.content, toBlob(record.vector), toBlob(record.tagsVector), record.containerTag, record.tags || null, record.type || null, record.createdAt, record.updatedAt, record.metadata || null, record.displayName || null, record.userName || null, record.userEmail || null, record.projectPath || null, record.projectName || null, record.gitRepoUrl || null);
|
|
149
|
+
});
|
|
150
|
+
insertMemory();
|
|
151
|
+
// Vector index update (outside transaction — vector backend is async/in-memory)
|
|
152
|
+
try {
|
|
153
|
+
const backend = await vectorSearch.getBackend();
|
|
154
|
+
await backend.insert({ id: record.id, vector: record.vector, shard, kind: "content" });
|
|
155
|
+
if (record.tagsVector) {
|
|
156
|
+
await backend.insert({ id: record.id, vector: record.tagsVector, shard, kind: "tags" });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
// Rollback SQLite insert on vector backend failure
|
|
161
|
+
db.prepare(`DELETE FROM memories WHERE id = ?`).run(record.id);
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
138
164
|
shardManager.incrementVectorCount(shard.id);
|
|
139
165
|
return { success: true, id };
|
|
140
166
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/services/embedding.ts"],"names":[],"mappings":"AA6CA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,WAAW,CAA8B;IAC1C,UAAU,EAAE,OAAO,CAAS;IACnC,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,eAAe,CAAuB;IAE9C,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAOhC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAOzD,eAAe;
|
|
1
|
+
{"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../src/services/embedding.ts"],"names":[],"mappings":"AA6CA,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,WAAW,CAA8B;IAC1C,UAAU,EAAE,OAAO,CAAS;IACnC,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,eAAe,CAAuB;IAE9C,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAOhC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YAOzD,eAAe;IA2CvB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAmD1C,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI3D,UAAU,IAAI,IAAI;CAGnB;AAED,eAAO,MAAM,gBAAgB,kBAAiC,CAAC"}
|
|
@@ -57,9 +57,26 @@ export class EmbeddingService {
|
|
|
57
57
|
async initializeModel(progressCallback) {
|
|
58
58
|
try {
|
|
59
59
|
if (CONFIG.embeddingApiUrl && CONFIG.embeddingApiKey) {
|
|
60
|
+
// Send a probe request to verify the API endpoint is actually reachable
|
|
61
|
+
// Uses a minimal embedding of "ping" to test the full request pipeline
|
|
62
|
+
const probeResponse = await withTimeout(fetch(`${CONFIG.embeddingApiUrl}/embeddings`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
Authorization: `Bearer ${CONFIG.embeddingApiKey}`,
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
input: "ping",
|
|
70
|
+
model: CONFIG.embeddingModel,
|
|
71
|
+
}),
|
|
72
|
+
}), TIMEOUT_MS);
|
|
73
|
+
if (!probeResponse.ok) {
|
|
74
|
+
throw new Error(`Embedding API health check failed: ${probeResponse.status} ${probeResponse.statusText}`);
|
|
75
|
+
}
|
|
60
76
|
this.isWarmedUp = true;
|
|
61
77
|
return;
|
|
62
78
|
}
|
|
79
|
+
// Local model path
|
|
63
80
|
const { pipeline } = await ensureTransformersLoaded();
|
|
64
81
|
this.pipe = await pipeline("feature-extraction", CONFIG.embeddingModel, {
|
|
65
82
|
progress_callback: progressCallback,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/connection-manager.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,QAAQ,uDAAgB,CAAC;AAE/B,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAqD;IAExE,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAarB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,QAAQ,CAAC,SAAS;IAiBxD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASrC,QAAQ,IAAI,IAAI;IAYhB,aAAa,IAAI,IAAI;CAStB;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-bootstrap.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/sqlite-bootstrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sqlite-bootstrap.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/sqlite-bootstrap.ts"],"names":[],"mappings":"AAmBA,KAAK,YAAY,GAAG,KAAK,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC;AAM1E,wBAAgB,WAAW,IAAI,YAAY,CA2D1C"}
|
|
@@ -1,8 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite binding bootstrap — works under Bun and Node.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. Bun runtime → `bun:sqlite` (built-in, fastest, zero-install)
|
|
6
|
+
* 2. Node runtime → `node:sqlite` `DatabaseSync` (built-in, Node 22.5+ experimental,
|
|
7
|
+
* stable in Node 24+)
|
|
8
|
+
* 3. Fallback → `better-sqlite3` (peer dependency, full native binary)
|
|
9
|
+
*
|
|
10
|
+
* Required because opencode 1.15.x loads plugins under Node, not Bun — `bun:sqlite`
|
|
11
|
+
* is a Bun-only built-in and Node's ESM loader rejects the `bun:` URL scheme.
|
|
12
|
+
*
|
|
13
|
+
* The detection runs once at first call; the resolved Database class is cached.
|
|
14
|
+
*/
|
|
15
|
+
import { createRequire } from "node:module";
|
|
1
16
|
let Database;
|
|
17
|
+
const isBun = typeof globalThis.Bun !== "undefined";
|
|
2
18
|
export function getDatabase() {
|
|
3
|
-
if (
|
|
4
|
-
|
|
5
|
-
|
|
19
|
+
if (Database)
|
|
20
|
+
return Database;
|
|
21
|
+
const req = createRequire(import.meta.url);
|
|
22
|
+
if (isBun) {
|
|
23
|
+
Database = req("bun:sqlite").Database;
|
|
24
|
+
return Database;
|
|
25
|
+
}
|
|
26
|
+
// Node runtime — try built-in `node:sqlite` first. It exposes `DatabaseSync`
|
|
27
|
+
// with the synchronous prepare/all/get/close API surface that matches
|
|
28
|
+
// bun:sqlite. One gap: bun:sqlite (and better-sqlite3) expose `db.run(sql)`
|
|
29
|
+
// for executing a single SQL statement without bindings — used throughout
|
|
30
|
+
// this project for PRAGMA and CREATE INDEX setup. `node:sqlite`'s
|
|
31
|
+
// DatabaseSync uses `db.exec(sql)` for that surface, so we subclass to
|
|
32
|
+
// alias `db.run(sql)` onto `db.exec(sql)` (param-bound `db.run(sql, ...)`
|
|
33
|
+
// is preserved for any future callers, falling back to a prepared statement).
|
|
34
|
+
try {
|
|
35
|
+
const DatabaseSync = req("node:sqlite")
|
|
36
|
+
.DatabaseSync;
|
|
37
|
+
class DatabaseSyncCompat extends DatabaseSync {
|
|
38
|
+
run(sql, ...params) {
|
|
39
|
+
if (params.length === 0) {
|
|
40
|
+
return this.exec(sql);
|
|
41
|
+
}
|
|
42
|
+
return this.prepare(sql).run(...params);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
Database = DatabaseSyncCompat;
|
|
46
|
+
return Database;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// node:sqlite isn't available (Node < 22.5, or experimental flag not set
|
|
50
|
+
// in some embedded runtimes). Fall back to better-sqlite3 — wire-compatible
|
|
51
|
+
// API, requires a native postinstall but ships prebuilt binaries for
|
|
52
|
+
// common platforms.
|
|
53
|
+
try {
|
|
54
|
+
const betterSqlite = req("better-sqlite3");
|
|
55
|
+
Database = betterSqlite;
|
|
56
|
+
return Database;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw new Error("opencode-mem: no SQLite binding available. Install better-sqlite3, " +
|
|
60
|
+
"or run on Node ≥22.5 with `--experimental-sqlite`, or use Bun. " +
|
|
61
|
+
`Underlying error: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
}
|
|
6
63
|
}
|
|
7
|
-
return Database;
|
|
8
64
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MemoryRecord, SearchResult, ShardInfo } from "./types.js";
|
|
2
2
|
import type { VectorBackend } from "../vector-backends/types.js";
|
|
3
|
-
declare const Database:
|
|
3
|
+
declare const Database: new (filename?: string, options?: unknown) => unknown;
|
|
4
4
|
type DatabaseType = typeof Database.prototype;
|
|
5
5
|
export declare class VectorSearch {
|
|
6
6
|
private readonly backendPromise;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,QAAA,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../../src/services/sqlite/vector-search.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAEjE,QAAA,MAAM,QAAQ,uDAAgB,CAAC;AAC/B,KAAK,YAAY,GAAG,OAAO,QAAQ,CAAC,SAAS,CAAC;AAM9C,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgB;gBAEpC,OAAO,CAAC,EAAE,aAAa,EAAE,eAAe,GAAE,aAAsC;YAO9E,UAAU;IAIlB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAyCtF,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;IA8HpB,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;IAUlF,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;IAkBhB,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;IAmB1E,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,oBAAoB,CACxB,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAgBV,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1D;AAED,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-server.d.ts","sourceRoot":"","sources":["../../src/services/web-server.ts"],"names":[],"mappings":"AA2HA,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,CAAqC;IACnD,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"}
|
|
@@ -1,8 +1,69 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
2
4
|
import { join, dirname } from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { log } from "./logger.js";
|
|
5
7
|
import { handleListTags, handleListMemories, handleAddMemory, handleDeleteMemory, handleBulkDelete, handleUpdateMemory, handleSearch, handleStats, handlePinMemory, handleUnpinMemory, handleRunCleanup, handleRunDeduplication, handleDetectMigration, handleRunMigration, handleDetectTagMigration, handleRunTagMigrationBatch, handleGetTagMigrationProgress, handleDeletePrompt, handleBulkDeletePrompts, handleGetUserProfile, handleGetProfileChangelog, handleGetProfileSnapshot, handleRefreshProfile, } from "./api-handlers.js";
|
|
8
|
+
const isBun = typeof globalThis.Bun !== "undefined";
|
|
9
|
+
function serveFetch(opts) {
|
|
10
|
+
if (isBun) {
|
|
11
|
+
const bunHandle = globalThis.Bun.serve({
|
|
12
|
+
port: opts.port,
|
|
13
|
+
hostname: opts.hostname,
|
|
14
|
+
fetch: opts.fetch,
|
|
15
|
+
});
|
|
16
|
+
return { stop: () => bunHandle.stop() };
|
|
17
|
+
}
|
|
18
|
+
// Node path: wrap node:http around the fetch-style handler. The adapter
|
|
19
|
+
// converts IncomingMessage → Web Request and Web Response → ServerResponse.
|
|
20
|
+
// Bodies stream both directions via the WHATWG Streams ↔ Node Streams
|
|
21
|
+
// helpers that ship with Node 18+.
|
|
22
|
+
const server = createServer(async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const url = `http://${opts.hostname}:${opts.port}${req.url ?? "/"}`;
|
|
25
|
+
const method = req.method ?? "GET";
|
|
26
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
27
|
+
const webReq = new Request(url, {
|
|
28
|
+
method,
|
|
29
|
+
headers: req.headers,
|
|
30
|
+
body: hasBody ? Readable.toWeb(req) : undefined,
|
|
31
|
+
// `duplex: "half"` is required by Node fetch when sending a body
|
|
32
|
+
// stream. Cast keeps TS happy on older lib.dom.d.ts revisions.
|
|
33
|
+
...(hasBody ? { duplex: "half" } : {}),
|
|
34
|
+
});
|
|
35
|
+
const webRes = await opts.fetch(webReq);
|
|
36
|
+
res.statusCode = webRes.status;
|
|
37
|
+
webRes.headers.forEach((value, name) => res.setHeader(name, value));
|
|
38
|
+
if (webRes.body) {
|
|
39
|
+
Readable.fromWeb(webRes.body).pipe(res);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
res.end();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!res.headersSent) {
|
|
47
|
+
res.statusCode = 500;
|
|
48
|
+
res.setHeader("Content-Type", "text/plain");
|
|
49
|
+
}
|
|
50
|
+
res.end(`Internal Server Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// Surface EADDRINUSE synchronously so callers can detect the
|
|
54
|
+
// already-running-instance case the same way they do under Bun.
|
|
55
|
+
let listenError;
|
|
56
|
+
server.on("error", (err) => {
|
|
57
|
+
if (err.code === "EADDRINUSE") {
|
|
58
|
+
listenError = err;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
server.listen(opts.port, opts.hostname);
|
|
62
|
+
if (listenError) {
|
|
63
|
+
throw listenError;
|
|
64
|
+
}
|
|
65
|
+
return { stop: () => server.close() };
|
|
66
|
+
}
|
|
6
67
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
68
|
const __dirname = dirname(__filename);
|
|
8
69
|
export class WebServer {
|
|
@@ -30,7 +91,7 @@ export class WebServer {
|
|
|
30
91
|
return;
|
|
31
92
|
}
|
|
32
93
|
try {
|
|
33
|
-
this.server =
|
|
94
|
+
this.server = serveFetch({
|
|
34
95
|
port: this.config.port,
|
|
35
96
|
hostname: this.config.host,
|
|
36
97
|
fetch: this.handleRequest.bind(this),
|
package/dist/web/app.js
CHANGED
|
@@ -645,7 +645,8 @@ function showRefreshIndicator(show) {
|
|
|
645
645
|
|
|
646
646
|
function formatDate(isoString) {
|
|
647
647
|
const date = new Date(isoString);
|
|
648
|
-
const
|
|
648
|
+
const lang = getLanguage();
|
|
649
|
+
const locale = lang === "zh" ? "zh-CN" : lang === "ar" ? "ar-SA" : "en-US";
|
|
649
650
|
return date.toLocaleString(locale, {
|
|
650
651
|
year: "numeric",
|
|
651
652
|
month: "short",
|
|
@@ -1141,10 +1142,12 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
1141
1142
|
});
|
|
1142
1143
|
|
|
1143
1144
|
document.getElementById("lang-toggle").addEventListener("click", () => {
|
|
1144
|
-
const
|
|
1145
|
+
const langCycle = ["en", "zh", "ar"];
|
|
1146
|
+
const currentLang = getLanguage();
|
|
1147
|
+
const currentIndex = langCycle.indexOf(currentLang);
|
|
1148
|
+
const newLang = langCycle[(currentIndex + 1) % langCycle.length];
|
|
1145
1149
|
setLanguage(newLang);
|
|
1146
1150
|
document.getElementById("lang-toggle").textContent = newLang.toUpperCase();
|
|
1147
|
-
// Re-render dynamic content
|
|
1148
1151
|
loadMemories();
|
|
1149
1152
|
loadStats();
|
|
1150
1153
|
if (state.currentView === "profile") loadUserProfile();
|
package/dist/web/i18n.js
CHANGED
|
@@ -211,6 +211,151 @@ const translations = {
|
|
|
211
211
|
"migration-mismatch-details":
|
|
212
212
|
"模型不匹配:配置使用 {configDimensions}D ({configModel}),但{shardInfo}。",
|
|
213
213
|
},
|
|
214
|
+
ar: {
|
|
215
|
+
title: "┌─ مستكشف ذاكرة OpenCode ─┐",
|
|
216
|
+
"tab-project": "ذكريات المشروع",
|
|
217
|
+
"tab-profile": "ملف المستخدم",
|
|
218
|
+
|
|
219
|
+
"label-tag": "الوسم:",
|
|
220
|
+
"label-type": "النوع:",
|
|
221
|
+
"label-tags": "الوسوم:",
|
|
222
|
+
"label-content": "المحتوى:",
|
|
223
|
+
|
|
224
|
+
"btn-cleanup": "تنظيف",
|
|
225
|
+
"btn-deduplicate": "إزالة التكرار",
|
|
226
|
+
"btn-delete-selected": "حذف المحدد",
|
|
227
|
+
"btn-select-all": "تحديد الصفحة",
|
|
228
|
+
"btn-deselect-all": "إلغاء التحديد",
|
|
229
|
+
"btn-add-memory": "إضافة ذكرى",
|
|
230
|
+
|
|
231
|
+
"section-project": "└─ ذكريات المشروع ({count}) ──",
|
|
232
|
+
"section-profile": "└─ ملف المستخدم ──",
|
|
233
|
+
"section-add": "└─ إضافة ذكرى جديدة ──",
|
|
234
|
+
|
|
235
|
+
"opt-all-tags": "جميع الوسوم",
|
|
236
|
+
"opt-select-tag": "اختر وسمًا",
|
|
237
|
+
"opt-other": "أخرى",
|
|
238
|
+
"opt-feature": "ميزة",
|
|
239
|
+
"opt-bug-fix": "إصلاح خطأ",
|
|
240
|
+
"opt-refactor": "إعادة هيكلة",
|
|
241
|
+
"opt-architecture": "معمارية",
|
|
242
|
+
"opt-rule": "قاعدة",
|
|
243
|
+
"opt-documentation": "توثيق",
|
|
244
|
+
"opt-discussion": "نقاش",
|
|
245
|
+
"opt-analysis": "تحليل",
|
|
246
|
+
"opt-configuration": "إعدادات",
|
|
247
|
+
|
|
248
|
+
"modal-edit-title": "تعديل الذكرى",
|
|
249
|
+
"modal-migration-title": "ترحيل وسوم الذكريات",
|
|
250
|
+
"modal-changelog-title": "سجل إصدارات الملف الشخصي",
|
|
251
|
+
|
|
252
|
+
"btn-cancel": "إلغاء",
|
|
253
|
+
"btn-save": "حفظ التغييرات",
|
|
254
|
+
"btn-start-migration": "بدء الترحيل",
|
|
255
|
+
|
|
256
|
+
"loading-init": "جاري التهيئة...",
|
|
257
|
+
"loading-profile": "جاري تحميل الملف الشخصي...",
|
|
258
|
+
"loading-changelog": "جاري تحميل السجل...",
|
|
259
|
+
|
|
260
|
+
"migration-mismatch": "تم اكتشاف عدم تطابق في أبعاد النموذج!",
|
|
261
|
+
"migration-understand":
|
|
262
|
+
"أفهم أن هذه العملية غير قابلة للتراجع وستؤثر على جميع الذكريات المخزنة",
|
|
263
|
+
|
|
264
|
+
"btn-fresh-start": "بداية جديدة (حذف الكل)",
|
|
265
|
+
"btn-reembed": "إعادة إنشاء المتجهات (مع الاحتفاظ بالبيانات)",
|
|
266
|
+
|
|
267
|
+
"migration-note":
|
|
268
|
+
"يرجى عدم إغلاق المتصفح. سيتم إعادة فهرسة الذكريات باستخدام وسوم تقنية لتحسين دقة البحث.",
|
|
269
|
+
|
|
270
|
+
"placeholder-search": "ابحث في الذكريات...",
|
|
271
|
+
"placeholder-tags": "react, hooks, auth (مفصولة بفواصل)",
|
|
272
|
+
"placeholder-content": "أدخل محتوى الذكرى...",
|
|
273
|
+
|
|
274
|
+
"toast-add-success": "تمت إضافة الذكرى بنجاح",
|
|
275
|
+
"toast-add-error": "المحتوى والوسم مطلوبان",
|
|
276
|
+
"toast-add-failed": "فشلت إضافة الذكرى",
|
|
277
|
+
|
|
278
|
+
"toast-delete-success": "تم حذف الذكرى بنجاح",
|
|
279
|
+
"toast-delete-failed": "فشل حذف الذكرى",
|
|
280
|
+
|
|
281
|
+
"toast-update-success": "تم تحديث الذكرى بنجاح",
|
|
282
|
+
"toast-update-failed": "فشل تحديث الذكرى",
|
|
283
|
+
|
|
284
|
+
"toast-cleanup-success": "اكتملت عملية التنظيف بنجاح",
|
|
285
|
+
"toast-cleanup-failed": "فشلت عملية التنظيف",
|
|
286
|
+
|
|
287
|
+
"toast-dedup-success": "اكتملت إزالة التكرار بنجاح",
|
|
288
|
+
"toast-dedup-failed": "فشلت إزالة التكرار",
|
|
289
|
+
|
|
290
|
+
"toast-bulk-delete-success": "تم حذف الذكريات المحددة بنجاح",
|
|
291
|
+
"toast-bulk-delete-failed": "فشل حذف الذكريات المحددة",
|
|
292
|
+
|
|
293
|
+
"toast-migration-success": "اكتملت عملية الترحيل بنجاح",
|
|
294
|
+
"toast-migration-failed": "فشلت عملية الترحيل",
|
|
295
|
+
|
|
296
|
+
"toast-fresh-start-success": "تمت البداية الجديدة بنجاح",
|
|
297
|
+
"toast-fresh-start-failed": "فشلت البداية الجديدة",
|
|
298
|
+
|
|
299
|
+
"confirm-delete": "هل تريد حذف هذه الذكرى؟",
|
|
300
|
+
"confirm-delete-pair": "هل تريد حذف هذه الذكرى والموجه المرتبط بها؟",
|
|
301
|
+
"confirm-delete-prompt": "هل تريد حذف هذا الموجه والذكرى المرتبطة به؟",
|
|
302
|
+
|
|
303
|
+
"confirm-bulk-delete": "هل تريد حذف {count} من الذكريات المحددة؟",
|
|
304
|
+
|
|
305
|
+
"confirm-cleanup": "سيؤدي هذا إلى حذف جميع الذكريات التي لم تعد ذات صلة. هل تريد المتابعة؟",
|
|
306
|
+
|
|
307
|
+
"confirm-dedup": "سيؤدي هذا إلى دمج الذكريات المتكررة أو المتشابهة جدًا. هل تريد المتابعة؟",
|
|
308
|
+
|
|
309
|
+
"text-selected": "تم تحديد {count}",
|
|
310
|
+
"text-page": "الصفحة {current} من {total}",
|
|
311
|
+
"text-total": "الإجمالي: {count}",
|
|
312
|
+
|
|
313
|
+
"empty-memories": "لم يتم العثور على ذكريات",
|
|
314
|
+
"empty-changelog": "لا يوجد سجل تغييرات",
|
|
315
|
+
|
|
316
|
+
"status-cleanup": "جاري التنظيف...",
|
|
317
|
+
"status-dedup": "جاري إزالة التكرار...",
|
|
318
|
+
"status-migration-init": "جاري تهيئة الترحيل...",
|
|
319
|
+
"status-migration-progress": "جاري الترحيل... {current}/{total}",
|
|
320
|
+
|
|
321
|
+
"profile-version": "الإصدار",
|
|
322
|
+
"profile-prompts": "الموجهات",
|
|
323
|
+
"profile-updated": "آخر تحديث",
|
|
324
|
+
"profile-preferences": "التفضيلات",
|
|
325
|
+
"profile-patterns": "الأنماط",
|
|
326
|
+
"profile-workflows": "سير العمل",
|
|
327
|
+
|
|
328
|
+
"badge-prompt": "موجه المستخدم",
|
|
329
|
+
"badge-memory": "ذكرى",
|
|
330
|
+
"badge-pinned": "مثبتة",
|
|
331
|
+
"badge-linked": "مرتبطة",
|
|
332
|
+
|
|
333
|
+
"date-created": "تاريخ الإنشاء:",
|
|
334
|
+
"date-updated": "تاريخ التحديث:",
|
|
335
|
+
|
|
336
|
+
"empty-preferences": "لم يتم تعلم أي تفضيلات بعد",
|
|
337
|
+
"empty-patterns": "لم يتم اكتشاف أي أنماط بعد",
|
|
338
|
+
"empty-workflows": "لم يتم التعرف على أي سير عمل بعد",
|
|
339
|
+
|
|
340
|
+
"btn-delete-pair": "حذف الزوج",
|
|
341
|
+
"btn-delete": "حذف",
|
|
342
|
+
|
|
343
|
+
"text-generated-above": "تم إنشاء الذكرى أعلاه",
|
|
344
|
+
"text-from-below": "من الموجه أدناه",
|
|
345
|
+
|
|
346
|
+
"btn-refresh": "تحديث",
|
|
347
|
+
|
|
348
|
+
"migration-found-tags": "تم العثور على {count} من الذكريات التي تحتاج إلى وسوم تقنية.",
|
|
349
|
+
|
|
350
|
+
"migration-stopped": "تم إيقاف الترحيل: تم الوصول إلى الحد الأقصى للمحاولات",
|
|
351
|
+
|
|
352
|
+
"migration-shards-mismatch": "{count} من الأجزاء تحتوي على أبعاد مختلفة",
|
|
353
|
+
|
|
354
|
+
"migration-dimension-mismatch": "تم اكتشاف عدم تطابق في الأبعاد",
|
|
355
|
+
|
|
356
|
+
"migration-mismatch-details":
|
|
357
|
+
"عدم تطابق النموذج: يستخدم الإعداد {configDimensions}D ({configModel}) بينما {shardInfo}.",
|
|
358
|
+
},
|
|
214
359
|
};
|
|
215
360
|
|
|
216
361
|
function getLanguage() {
|
|
@@ -219,6 +364,11 @@ function getLanguage() {
|
|
|
219
364
|
|
|
220
365
|
function setLanguage(lang) {
|
|
221
366
|
localStorage.setItem("opencode-mem-lang", lang);
|
|
367
|
+
|
|
368
|
+
document.documentElement.dir = lang === "ar" ? "rtl" : "ltr";
|
|
369
|
+
|
|
370
|
+
document.documentElement.lang = lang;
|
|
371
|
+
|
|
222
372
|
applyLanguage();
|
|
223
373
|
}
|
|
224
374
|
|