From 521b5a4f95d4f24f7abee53be70021d9c1d086cd Mon Sep 17 00:00:00 2001
From: zhangmo8
Date: Mon, 22 Jun 2026 11:03:24 +0800
Subject: [PATCH 1/3] fix(memory): harden memory followups
---
docs/issues/pr1794-memory-hardening/plan.md | 38 ++
docs/issues/pr1794-memory-hardening/spec.md | 41 ++
docs/issues/pr1794-memory-hardening/tasks.md | 16 +
.../presenter/agentRuntimePresenter/index.ts | 5 -
.../databaseSecurityPresenter/index.ts | 87 ++++
.../presenter/memoryPresenter/extraction.ts | 22 +-
src/main/presenter/memoryPresenter/index.ts | 66 ++-
src/main/presenter/sqlitePresenter/index.ts | 11 +-
.../sqlitePresenter/tables/agentMemory.ts | 141 +++++--
.../tables/deepchatTapeSearchProjection.ts | 74 +++-
.../settings/components/MemoryConfigPanel.vue | 394 ++++++++++--------
.../components/MemoryManagerPanel.vue | 69 ++-
.../settings/components/MemorySettings.vue | 37 +-
src/renderer/src/i18n/da-DK/settings.json | 15 +-
src/renderer/src/i18n/de-DE/settings.json | 15 +-
src/renderer/src/i18n/en-US/settings.json | 15 +-
src/renderer/src/i18n/es-ES/settings.json | 15 +-
src/renderer/src/i18n/fa-IR/settings.json | 15 +-
src/renderer/src/i18n/fr-FR/settings.json | 15 +-
src/renderer/src/i18n/he-IL/settings.json | 15 +-
src/renderer/src/i18n/id-ID/settings.json | 15 +-
src/renderer/src/i18n/it-IT/settings.json | 15 +-
src/renderer/src/i18n/ja-JP/settings.json | 15 +-
src/renderer/src/i18n/ko-KR/settings.json | 15 +-
src/renderer/src/i18n/ms-MY/settings.json | 15 +-
src/renderer/src/i18n/pl-PL/settings.json | 15 +-
src/renderer/src/i18n/pt-BR/settings.json | 15 +-
src/renderer/src/i18n/ru-RU/settings.json | 15 +-
src/renderer/src/i18n/tr-TR/settings.json | 15 +-
src/renderer/src/i18n/vi-VN/settings.json | 15 +-
src/renderer/src/i18n/zh-CN/settings.json | 15 +-
src/renderer/src/i18n/zh-HK/settings.json | 15 +-
src/renderer/src/i18n/zh-TW/settings.json | 15 +-
test/main/presenter/memoryExtraction.test.ts | 43 +-
34 files changed, 1024 insertions(+), 320 deletions(-)
create mode 100644 docs/issues/pr1794-memory-hardening/plan.md
create mode 100644 docs/issues/pr1794-memory-hardening/spec.md
create mode 100644 docs/issues/pr1794-memory-hardening/tasks.md
diff --git a/docs/issues/pr1794-memory-hardening/plan.md b/docs/issues/pr1794-memory-hardening/plan.md
new file mode 100644
index 000000000..39a4389cd
--- /dev/null
+++ b/docs/issues/pr1794-memory-hardening/plan.md
@@ -0,0 +1,38 @@
+# Plan
+
+## Core memory runtime
+
+1. Change extraction parsing to return a discriminated result so parse errors can propagate as `ok:false` from `extractAndStore()`.
+2. Filter memory extraction spans to user-visible text only; exclude assistant reasoning fields.
+3. Add a current-embedding fingerprint guard and/or queue wait before reindex reset to prevent stale drains from writing old vectors.
+4. Gate `restoreMemory()` with `canWriteAgentMemory()` and delete vectors when rows are archived/forgotten.
+5. Update memory FTS query construction to tokenize multi-word queries while retaining safe quoting.
+
+## Database and FTS
+
+1. Extend database security copy to preserve non-shadow indexes and triggers, or run a schema repair/rebuild pass after copy.
+2. Add FTS meta/version/tokenizer tracking to agent memory FTS and rebuild when capability differs.
+3. Make tape search FTS replacement/deletion stable by using a deterministic rowid or external-content style rebuild/delete path.
+4. Make clear/reset paths tolerate missing FTS/meta tables.
+5. Seed schema version for newly created databases or otherwise avoid running historical migrations against freshly-created latest schemas.
+
+## Renderer UI
+
+1. Add top-level MemorySettings error/finally handling.
+2. Add request-id guards to MemoryManagerPanel refresh.
+3. Surface search errors distinctly from empty results.
+4. Rename default destructive memory operations to archive/restore semantics; keep permanent delete as an explicit dangerous action if still exposed.
+5. Move/label advanced retrieval tuning to reduce accidental misuse.
+
+## Tests
+
+- Main tests for extraction parse failure cursor behavior and reasoning exclusion.
+- Main tests for reindex/drain stale guard and restore disabled behavior.
+- SQLite tests for FTS tokenizer rebuild and stale token replacement.
+- Database security migration tests for indexes/triggers if existing harness supports it.
+- Renderer tests for MemorySettings error state and stale refresh/search behavior.
+
+## Compatibility
+
+- Existing rows remain valid. FTS rebuilds are derived from canonical SQLite rows/projection.
+- Archived rows remain available for restore; sidecar vectors are treated as cache and can be regenerated.
diff --git a/docs/issues/pr1794-memory-hardening/spec.md b/docs/issues/pr1794-memory-hardening/spec.md
new file mode 100644
index 000000000..6e1d2735d
--- /dev/null
+++ b/docs/issues/pr1794-memory-hardening/spec.md
@@ -0,0 +1,41 @@
+# PR1794 Memory Hardening
+
+## User need
+
+PR #1794 adds a broad long-term memory system. Before merge, the implementation must avoid silent memory loss, memory contamination, data/index corruption, confusing destructive UI behavior, and obvious reliability regressions.
+
+## Goal
+
+Fix the must-fix and medium-high priority review findings for PR #1794 while preserving the overall tape-native memory design.
+
+## Acceptance criteria
+
+- Memory extraction does not advance session memory cursors when the LLM extraction output cannot be parsed as a valid result.
+- Assistant reasoning/internal thinking fields are excluded from memory extraction spans.
+- Embedding reindex/reset cannot be raced by an older in-flight embedding drain writing stale vectors.
+- Disabling memory consistently prevents restore/write paths from scheduling new embeddings, unless explicitly documented as read-only management.
+- Archive/forget removes stale vectors from the sidecar or otherwise compacts them so archived rows do not bloat vector recall.
+- Memory keyword search supports tokenized multi-word queries, not only exact phrase queries.
+- SQLCipher/encryption copy preserves or rebuilds indexes/triggers required by memory/tape tables.
+- FTS indexes have a version/tokenizer rebuild path so runtime capability changes do not leave stale FTS schemas.
+- Tape search FTS replacement/deletion does not leave stale tokens for replaced entries.
+- Memory settings UI recovers from load errors, avoids stale refresh overwrites, and makes archive vs permanent delete semantics clear.
+- Relevant unit tests cover the fixed behaviors.
+
+## Constraints
+
+- Keep changes focused on PR #1794 memory hardening; avoid unrelated refactors.
+- Preserve backward compatibility with existing local SQLite databases.
+- Do not introduce new runtime dependencies.
+- Main-process DB operations remain synchronous where existing SQLite presenter patterns require it.
+- UI strings must use i18n keys.
+
+## Non-goals
+
+- Redesigning the entire memory architecture.
+- Changing the public PR feature scope beyond hardening and safety fixes.
+- Shipping a complete vector compaction scheduler if immediate vector deletion on archive is sufficient.
+
+## Open questions
+
+None.
diff --git a/docs/issues/pr1794-memory-hardening/tasks.md b/docs/issues/pr1794-memory-hardening/tasks.md
new file mode 100644
index 000000000..84af6b236
--- /dev/null
+++ b/docs/issues/pr1794-memory-hardening/tasks.md
@@ -0,0 +1,16 @@
+# Tasks
+
+- [x] Core: make extraction parse failures retryable.
+- [x] Core: exclude assistant reasoning from extraction spans.
+- [x] Core: prevent stale embedding drains during reindex/reset.
+- [x] Core: gate restore and clean vector sidecar on archive/forget.
+- [x] Core: improve memory FTS tokenized search.
+- [x] DB: preserve/rebuild indexes and triggers during encrypted copy.
+- [x] DB: version/rebuild agent memory FTS tokenizer.
+- [x] DB: stabilize tape search FTS replacement/deletion.
+- [x] DB: harden clear/reset and new DB schema version initialization.
+- [x] UI: add MemorySettings error handling.
+- [x] UI: guard MemoryManagerPanel refresh and search errors.
+- [x] UI: clarify archive vs permanent delete and advanced settings.
+- [x] Tests: update memory extraction tests for `parseMemoryCandidates` union return.
+- [ ] Validation: `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, and `pnpm run typecheck` passed under Node v26 warning; `pnpm test` was stopped per user instruction after memoryPresenter failures surfaced.
diff --git a/src/main/presenter/agentRuntimePresenter/index.ts b/src/main/presenter/agentRuntimePresenter/index.ts
index 3d49d6ab2..bc84d1ef0 100644
--- a/src/main/presenter/agentRuntimePresenter/index.ts
+++ b/src/main/presenter/agentRuntimePresenter/index.ts
@@ -1992,13 +1992,8 @@ export class AgentRuntimePresenter implements IAgentImplementation {
const b = block as {
type?: string
content?: unknown
- reasoning_content?: unknown
- text?: unknown
}
if (b?.type === 'content' && typeof b.content === 'string') return b.content
- if (b?.type === 'reasoning_content' && typeof b.content === 'string') return b.content
- if (typeof b?.reasoning_content === 'string') return b.reasoning_content
- if (b?.type === 'reasoning' && typeof b.text === 'string') return b.text
return ''
})
.filter(Boolean)
diff --git a/src/main/presenter/databaseSecurityPresenter/index.ts b/src/main/presenter/databaseSecurityPresenter/index.ts
index 576018761..adcb9a35f 100644
--- a/src/main/presenter/databaseSecurityPresenter/index.ts
+++ b/src/main/presenter/databaseSecurityPresenter/index.ts
@@ -63,6 +63,22 @@ type SqliteSchemaRow = {
const quoteIdentifier = (value: string): string => `"${value.replace(/"/g, '""')}"`
const getMigrationLockPath = (dbPath: string): string => path.resolve(dbPath)
+const FTS_SCHEMA_OBJECT_NAME_PATTERN = /(^|_)fts($|_)/i
+const FTS_TRIGGER_NAME_PATTERN = /_(ai|ad|au)$/i
+
+function isFtsMaintenanceSchemaObject(row: SqliteSchemaRow & { tbl_name?: string }): boolean {
+ const sql = row.sql.toLowerCase()
+ return (
+ FTS_SCHEMA_OBJECT_NAME_PATTERN.test(row.name) ||
+ (row.tbl_name ? FTS_SCHEMA_OBJECT_NAME_PATTERN.test(row.tbl_name) : false) ||
+ /\b[a-z0-9_]+_fts\b/i.test(row.sql) ||
+ /\busing\s+fts[345]?\b/i.test(row.sql) ||
+ (row.type === 'trigger' &&
+ FTS_TRIGGER_NAME_PATTERN.test(row.name) &&
+ sql.includes('insert into'))
+ )
+}
+
export class DatabaseSecurityPresenter {
private readonly store: ElectronStore<{ metadata: DatabaseSecurityMetadata }>
private readonly dbPath: string
@@ -361,6 +377,7 @@ export class DatabaseSecurityPresenter {
)
}
+ this.copySchemaObjects(db)
this.copySqliteSequence(db)
db.exec('COMMIT')
} catch (error) {
@@ -369,6 +386,54 @@ export class DatabaseSecurityPresenter {
}
}
+ private copySchemaObjects(db: Database.Database): void {
+ for (const object of this.listMigratableSchemaObjects(db)) {
+ db.exec(this.qualifyCreateSchemaObjectSql(object))
+ }
+ }
+
+ private listMigratableSchemaObjects(db: Database.Database): SqliteSchemaRow[] {
+ const virtualTableNames = new Set(
+ (
+ db
+ .prepare(
+ `SELECT name, sql FROM sqlite_master
+ WHERE type = 'table'
+ AND sql IS NOT NULL
+ AND name NOT LIKE 'sqlite_%'`
+ )
+ .all() as SqliteSchemaRow[]
+ )
+ .filter((row) => /^CREATE\s+VIRTUAL\s+TABLE\s+/i.test(row.sql))
+ .map((row) => row.name)
+ )
+ const rows = db
+ .prepare(
+ `SELECT type, name, tbl_name, sql FROM sqlite_master
+ WHERE type IN ('index', 'trigger', 'view')
+ AND sql IS NOT NULL
+ AND name NOT LIKE 'sqlite_%'
+ ORDER BY CASE type WHEN 'index' THEN 0 WHEN 'trigger' THEN 1 ELSE 2 END, name ASC`
+ )
+ .all() as Array
+ return rows.filter((row) => {
+ if (shouldExcludeFromSqliteCopy(row.name)) return false
+ if (row.tbl_name && shouldExcludeFromSqliteCopy(row.tbl_name)) return false
+ if (isFtsMaintenanceSchemaObject(row)) return false
+ for (const virtualTableName of virtualTableNames) {
+ if (
+ row.name === virtualTableName ||
+ row.name.startsWith(`${virtualTableName}_`) ||
+ row.tbl_name === virtualTableName ||
+ row.sql.includes(virtualTableName)
+ ) {
+ return false
+ }
+ }
+ return true
+ })
+ }
+
private listMigratableTables(db: Database.Database): SqliteSchemaRow[] {
const rows = db
.prepare(
@@ -404,6 +469,28 @@ export class DatabaseSecurityPresenter {
)
}
+ private qualifyCreateSchemaObjectSql(row: SqliteSchemaRow): string {
+ if (row.type === 'index') {
+ return row.sql.replace(
+ /^CREATE\s+(UNIQUE\s+)?INDEX\s+(IF\s+NOT\s+EXISTS\s+)?/i,
+ (_match, unique: string | undefined, ifNotExists: string | undefined) =>
+ `CREATE ${unique ?? ''}INDEX ${ifNotExists ?? ''}${MIGRATION_TARGET_SCHEMA}.`
+ )
+ }
+ if (row.type === 'trigger') {
+ return row.sql.replace(
+ /^CREATE\s+(TEMP\s+|TEMPORARY\s+)?TRIGGER\s+(IF\s+NOT\s+EXISTS\s+)?/i,
+ (_match, temp: string | undefined, ifNotExists: string | undefined) =>
+ `CREATE ${temp ?? ''}TRIGGER ${ifNotExists ?? ''}${MIGRATION_TARGET_SCHEMA}.`
+ )
+ }
+ return row.sql.replace(
+ /^CREATE\s+(TEMP\s+|TEMPORARY\s+)?VIEW\s+(IF\s+NOT\s+EXISTS\s+)?/i,
+ (_match, temp: string | undefined, ifNotExists: string | undefined) =>
+ `CREATE ${temp ?? ''}VIEW ${ifNotExists ?? ''}${MIGRATION_TARGET_SCHEMA}.`
+ )
+ }
+
private copySqliteSequence(db: Database.Database): void {
const sourceSequence = db
.prepare("SELECT 1 FROM main.sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'")
diff --git a/src/main/presenter/memoryPresenter/extraction.ts b/src/main/presenter/memoryPresenter/extraction.ts
index 4c86839a6..0e45cc1f0 100644
--- a/src/main/presenter/memoryPresenter/extraction.ts
+++ b/src/main/presenter/memoryPresenter/extraction.ts
@@ -53,19 +53,27 @@ export function buildExtractionPrompt(spanText: string): string {
].join('\n')
}
-// Tolerant parse: code fences, surrounding noise, and missing fields all degrade to [].
-export function parseMemoryCandidates(raw: string): MemoryCandidate[] {
- if (!raw) return []
+export type MemoryCandidateParseResult =
+ | { ok: true; candidates: MemoryCandidate[] }
+ | {
+ ok: false
+ reason: 'empty-response' | 'missing-json-array' | 'invalid-json' | 'non-array'
+ }
+
+// Tolerant per-entry parse: surrounding noise and malformed entries are ignored, but malformed
+// top-level model output is reported so callers can retry instead of advancing durable cursors.
+export function parseMemoryCandidates(raw: string): MemoryCandidateParseResult {
+ if (typeof raw !== 'string' || !raw.trim()) return { ok: false, reason: 'empty-response' }
const jsonText = extractJsonArray(raw)
- if (!jsonText) return []
+ if (!jsonText) return { ok: false, reason: 'missing-json-array' }
let parsed: unknown
try {
parsed = JSON.parse(jsonText)
} catch {
- return []
+ return { ok: false, reason: 'invalid-json' }
}
- if (!Array.isArray(parsed)) return []
+ if (!Array.isArray(parsed)) return { ok: false, reason: 'non-array' }
const candidates: MemoryCandidate[] = []
for (const entry of parsed) {
@@ -78,7 +86,7 @@ export function parseMemoryCandidates(raw: string): MemoryCandidate[] {
candidates.push({ kind, content, importance })
if (candidates.length >= MAX_CANDIDATES) break
}
- return candidates
+ return { ok: true, candidates }
}
function clampImportance(value: unknown): number {
diff --git a/src/main/presenter/memoryPresenter/index.ts b/src/main/presenter/memoryPresenter/index.ts
index 673cda20a..c31777b90 100644
--- a/src/main/presenter/memoryPresenter/index.ts
+++ b/src/main/presenter/memoryPresenter/index.ts
@@ -477,6 +477,15 @@ export class MemoryPresenter implements MemoryRuntimePort {
this.isPendingEmbeddableRow(agentId, this.deps.repository.getById(record.memoryId))
)
if (!live.length) return { written: new Set(), usable: true }
+ const currentEmbedding = this.deps.resolveAgentConfig(agentId)?.memoryEmbedding
+ if (
+ !currentEmbedding?.providerId ||
+ !currentEmbedding?.modelId ||
+ embeddingFingerprint(currentEmbedding.providerId, currentEmbedding.modelId) !==
+ embeddingFingerprint(embedding.providerId, embedding.modelId)
+ ) {
+ return { written: new Set(), usable: true }
+ }
const store = await this.openVectorStoreLocked(
agentId,
{ providerId: embedding.providerId, modelId: embedding.modelId },
@@ -492,6 +501,17 @@ export class MemoryPresenter implements MemoryRuntimePort {
if (!this.canContinueAgentMemoryTask(agentId)) return
const fingerprint = embeddingFingerprint(embedding.providerId, embedding.modelId)
+ const currentEmbedding = this.deps.resolveAgentConfig(agentId)?.memoryEmbedding
+ const currentFingerprint =
+ currentEmbedding?.providerId && currentEmbedding?.modelId
+ ? embeddingFingerprint(currentEmbedding.providerId, currentEmbedding.modelId)
+ : null
+ if (currentFingerprint !== fingerprint) {
+ logger.info(
+ `[Memory] embedding config changed during drain for ${agentId}; discarding stale vectors`
+ )
+ return
+ }
for (const record of records) {
if (outcome.written.has(record.memoryId)) {
this.deps.repository.updatePendingEmbeddingStatus(agentId, record.memoryId, 'embedded', {
@@ -541,6 +561,11 @@ export class MemoryPresenter implements MemoryRuntimePort {
// `force` rebuilds an unusable on-disk store even with nothing to re-queue (the foreign file is
// itself what blocks recovery); otherwise skip the reset when there is no stale work.
if (!requeued && !force) return
+ // Wait for a drain that captured the previous embedding config before resetting the sidecar,
+ // otherwise stale vectors can be written into the freshly reset store.
+ const inFlightDrain = this.embeddingDrains.get(agentId)
+ if (inFlightDrain) await inFlightDrain.catch(() => undefined)
+ if (!this.canContinueAgentMemoryTask(agentId)) return
// Drop the stale-dimension store under the per-agent lock; the next embed rebuilds it.
await this.runExclusiveForAgent(agentId, async () => {
if (!this.canContinueAgentMemoryTask(agentId)) return
@@ -627,7 +652,12 @@ export class MemoryPresenter implements MemoryRuntimePort {
)
// Teardown may have begun during the extraction await; stop before any candidate processing.
if (!this.canWriteAgentMemory(input.agentId)) return { ok: true, createdIds: [] }
- const candidates = parseMemoryCandidates(response)
+ const parsed = parseMemoryCandidates(response)
+ if (!parsed.ok) {
+ logger.warn(`[Memory] extraction parse failed: ${parsed.reason}`)
+ return { ok: false }
+ }
+ const candidates = parsed.candidates
const options: WriteMemoriesOptions = {
agentId: input.agentId,
sourceSession: input.sourceSession ?? null,
@@ -1136,7 +1166,7 @@ export class MemoryPresenter implements MemoryRuntimePort {
restoreMemory(agentId: string, memoryId: string): boolean {
if (this.disposed) return false
this.assertSafeAgentId(agentId)
- if (!this.isManagedAgent(agentId)) return false
+ if (!this.canWriteAgentMemory(agentId)) return false
const row = this.deps.repository.getById(memoryId)
if (!row || row.agent_id !== agentId || row.status !== 'archived') return false
this.deps.repository.updateStatus(memoryId, 'pending_embedding')
@@ -1151,11 +1181,13 @@ export class MemoryPresenter implements MemoryRuntimePort {
async forgetMemory(agentId: string, memoryId: string): Promise {
if (this.disposed) return false
this.assertSafeAgentId(agentId)
- if (!this.isManagedAgent(agentId)) return false
+ if (!this.canWriteAgentMemory(agentId)) return false
const row = this.deps.repository.getById(memoryId)
if (!row || row.agent_id !== agentId) return false
if (row.status === 'archived') return true
this.deps.repository.archive(row.id, Date.now())
+ await this.deleteVectorsForMemoryIds(agentId, [memoryId])
+ if (this.disposed) return true
this.syncWorkingMemoryAfterMutation(agentId)
this.emitChanged(agentId, 'extract')
return true
@@ -2051,18 +2083,7 @@ export class MemoryPresenter implements MemoryRuntimePort {
if (row.kind !== 'working') {
this.syncWorkingMemoryAfterMutation(agentId)
}
- // Run the vector delete under the per-agent lock so dispose() awaits it (via vectorStoreLocks)
- // before closing the sidecar — otherwise a teardown landing mid-DELETE could close the DuckDB
- // connection while the statement runs. If teardown already began, skip it: the authoritative row
- // is gone and an orphaned vector is harmless (recall skips matches whose SQLite row is missing).
- await this.runExclusiveForAgent(agentId, async () => {
- if (this.disposed) return
- const store = await this.vectorStoreForAgent(agentId)
- if (!store) return
- await store.deleteByMemoryIds([memoryId]).catch((error) => {
- logger.warn(`[Memory] vector delete failed: ${String(error)}`)
- })
- })
+ await this.deleteVectorsForMemoryIds(agentId, [memoryId])
if (this.disposed) return true
this.emitChanged(agentId, 'delete')
return true
@@ -2119,6 +2140,21 @@ export class MemoryPresenter implements MemoryRuntimePort {
if (resetError) throw resetError
}
+ private async deleteVectorsForMemoryIds(agentId: string, memoryIds: string[]): Promise {
+ if (!memoryIds.length) return
+ // Run vector deletes under the per-agent lock so dispose() awaits them (via vectorStoreLocks)
+ // before closing the sidecar. If teardown already began, skip it: SQLite status is authoritative
+ // and recall ignores rows that are archived/deleted.
+ await this.runExclusiveForAgent(agentId, async () => {
+ if (this.disposed) return
+ const store = await this.vectorStoreForAgent(agentId)
+ if (!store) return
+ await store.deleteByMemoryIds(memoryIds).catch((error) => {
+ logger.warn(`[Memory] vector delete failed: ${String(error)}`)
+ })
+ })
+ }
+
private async settleDeletedAgentInFlight(agentId: string): Promise {
const reindexing = this.reindexing.get(agentId)
const backfilling = this.backfilling.get(agentId)
diff --git a/src/main/presenter/sqlitePresenter/index.ts b/src/main/presenter/sqlitePresenter/index.ts
index bfbfc61c9..85d690c88 100644
--- a/src/main/presenter/sqlitePresenter/index.ts
+++ b/src/main/presenter/sqlitePresenter/index.ts
@@ -240,6 +240,7 @@ export class SQLitePresenter implements ISQLitePresenter {
private dbPath: string
private password?: string
private destructiveInitializationRetryCount = 0
+ private databaseFileExistedBeforeOpen = false
constructor(dbPath: string, password?: string) {
this.dbPath = dbPath
@@ -302,6 +303,7 @@ export class SQLitePresenter implements ISQLitePresenter {
}
private initializeDatabase(): void {
+ this.databaseFileExistedBeforeOpen = fs.existsSync(this.dbPath)
this.db = openSQLiteDatabase(this.dbPath, this.password)
this.db.prepare('SELECT 1').get()
this.initTables()
@@ -496,6 +498,14 @@ export class SQLitePresenter implements ISQLitePresenter {
return Math.max(maxVersion, tableMaxVersion)
}, 0)
+ if (!this.databaseFileExistedBeforeOpen && this.currentVersion === 0 && latestVersion > 0) {
+ this.db
+ .prepare('INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)')
+ .run(latestVersion, Date.now())
+ this.currentVersion = latestVersion
+ return
+ }
+
// 只迁移未执行的版本
tables.forEach((table) => {
for (let version = this.currentVersion + 1; version <= latestVersion; version++) {
@@ -575,7 +585,6 @@ export class SQLitePresenter implements ISQLitePresenter {
DELETE FROM deepchat_tape_entries;
DELETE FROM deepchat_tape_search_projection;
DELETE FROM deepchat_tape_search_projection_meta;
- DELETE FROM deepchat_tape_search_fts_meta;
DELETE FROM deepchat_sessions;
DELETE FROM new_session_active_skills;
DELETE FROM new_session_disabled_agent_tools;
diff --git a/src/main/presenter/sqlitePresenter/tables/agentMemory.ts b/src/main/presenter/sqlitePresenter/tables/agentMemory.ts
index 43ad8e09b..5609d7e74 100644
--- a/src/main/presenter/sqlitePresenter/tables/agentMemory.ts
+++ b/src/main/presenter/sqlitePresenter/tables/agentMemory.ts
@@ -78,6 +78,9 @@ export interface AgentMemoryListOptions {
// persona lifecycle column; v35 adds conflict linkage.
const AGENT_MEMORY_SCHEMA_VERSION = 35
+const AGENT_MEMORY_FTS_META_KEY = 'agent_memory_fts'
+const AGENT_MEMORY_FTS_META_VERSION = 1
+
type FtsCapability = { available: boolean; tokenizer: 'trigram' | 'unicode61' }
const AGENT_MEMORY_INDEX_SQL = `
@@ -90,6 +93,14 @@ const AGENT_MEMORY_INDEX_SQL = `
WHERE provenance_key IS NOT NULL;
`
+function tokenizeSearchQuery(query: string): string[] {
+ return query
+ .trim()
+ .split(/\s+/u)
+ .map((term) => term.trim())
+ .filter(Boolean)
+}
+
function escapeLikePattern(value: string): string {
return value.replace(/[\\%_]/g, (character) => `\\${character}`)
}
@@ -210,6 +221,34 @@ export class AgentMemoryTable extends BaseTable {
return !!row
}
+ private readFtsMeta(): { schema_version: number; tokenizer: string } | undefined {
+ return this.db
+ .prepare('SELECT schema_version, tokenizer FROM agent_memory_fts_meta WHERE key = ?')
+ .get(AGENT_MEMORY_FTS_META_KEY) as { schema_version: number; tokenizer: string } | undefined
+ }
+
+ private writeFtsMeta(tokenizer: string): void {
+ this.db
+ .prepare(
+ `INSERT INTO agent_memory_fts_meta (key, schema_version, tokenizer, updated_at)
+ VALUES (?, ?, ?, ?)
+ ON CONFLICT(key) DO UPDATE SET
+ schema_version = excluded.schema_version,
+ tokenizer = excluded.tokenizer,
+ updated_at = excluded.updated_at`
+ )
+ .run(AGENT_MEMORY_FTS_META_KEY, AGENT_MEMORY_FTS_META_VERSION, tokenizer, Date.now())
+ }
+
+ private dropFtsIndex(): void {
+ this.db.exec(`
+ DROP TRIGGER IF EXISTS agent_memory_fts_ai;
+ DROP TRIGGER IF EXISTS agent_memory_fts_ad;
+ DROP TRIGGER IF EXISTS agent_memory_fts_au;
+ DROP TABLE IF EXISTS agent_memory_fts;
+ `)
+ }
+
// Creates the external-content FTS5 mirror of agent_memory and the triggers that keep it in
// sync, then backfills existing rows the first time it is built. Idempotent and a no-op when
// FTS5 is unavailable (search falls back to LIKE). superseded rows stay in the index and are
@@ -220,37 +259,63 @@ export class AgentMemoryTable extends BaseTable {
this.ftsReady = false
return
}
- const alreadyBuilt = this.ftsTableExists()
- this.db.exec(`
- CREATE VIRTUAL TABLE IF NOT EXISTS agent_memory_fts USING fts5(
- content,
- agent_id UNINDEXED,
- content='agent_memory',
- content_rowid='rowid',
- tokenize='${capability.tokenizer}'
- );
- CREATE TRIGGER IF NOT EXISTS agent_memory_fts_ai AFTER INSERT ON agent_memory BEGIN
- INSERT INTO agent_memory_fts(rowid, content, agent_id)
- VALUES (new.rowid, new.content, new.agent_id);
- END;
- CREATE TRIGGER IF NOT EXISTS agent_memory_fts_ad AFTER DELETE ON agent_memory BEGIN
- INSERT INTO agent_memory_fts(agent_memory_fts, rowid, content, agent_id)
- VALUES ('delete', old.rowid, old.content, old.agent_id);
- END;
- CREATE TRIGGER IF NOT EXISTS agent_memory_fts_au AFTER UPDATE OF content ON agent_memory BEGIN
- INSERT INTO agent_memory_fts(agent_memory_fts, rowid, content, agent_id)
- VALUES ('delete', old.rowid, old.content, old.agent_id);
- INSERT INTO agent_memory_fts(rowid, content, agent_id)
- VALUES (new.rowid, new.content, new.agent_id);
- END;
- `)
- if (!alreadyBuilt) {
- this.db.exec(
- `INSERT INTO agent_memory_fts(rowid, content, agent_id)
- SELECT rowid, content, agent_id FROM agent_memory;`
- )
+ try {
+ this.db.transaction(() => {
+ this.db.exec(`
+ CREATE TABLE IF NOT EXISTS agent_memory_fts_meta (
+ key TEXT PRIMARY KEY,
+ schema_version INTEGER NOT NULL,
+ tokenizer TEXT NOT NULL,
+ updated_at INTEGER NOT NULL
+ );
+ `)
+ const meta = this.readFtsMeta()
+ const alreadyBuilt = this.ftsTableExists()
+ if (
+ alreadyBuilt &&
+ (!meta ||
+ meta.schema_version !== AGENT_MEMORY_FTS_META_VERSION ||
+ meta.tokenizer !== capability.tokenizer)
+ ) {
+ this.dropFtsIndex()
+ }
+ const shouldBackfill = !this.ftsTableExists()
+ this.db.exec(`
+ CREATE VIRTUAL TABLE IF NOT EXISTS agent_memory_fts USING fts5(
+ content,
+ agent_id UNINDEXED,
+ content='agent_memory',
+ content_rowid='rowid',
+ tokenize='${capability.tokenizer}'
+ );
+ CREATE TRIGGER IF NOT EXISTS agent_memory_fts_ai AFTER INSERT ON agent_memory BEGIN
+ INSERT INTO agent_memory_fts(rowid, content, agent_id)
+ VALUES (new.rowid, new.content, new.agent_id);
+ END;
+ CREATE TRIGGER IF NOT EXISTS agent_memory_fts_ad AFTER DELETE ON agent_memory BEGIN
+ INSERT INTO agent_memory_fts(agent_memory_fts, rowid, content, agent_id)
+ VALUES ('delete', old.rowid, old.content, old.agent_id);
+ END;
+ CREATE TRIGGER IF NOT EXISTS agent_memory_fts_au AFTER UPDATE OF content ON agent_memory BEGIN
+ INSERT INTO agent_memory_fts(agent_memory_fts, rowid, content, agent_id)
+ VALUES ('delete', old.rowid, old.content, old.agent_id);
+ INSERT INTO agent_memory_fts(rowid, content, agent_id)
+ VALUES (new.rowid, new.content, new.agent_id);
+ END;
+ `)
+ if (shouldBackfill) {
+ this.db.exec(
+ `INSERT INTO agent_memory_fts(rowid, content, agent_id)
+ SELECT rowid, content, agent_id FROM agent_memory;`
+ )
+ }
+ this.writeFtsMeta(capability.tokenizer)
+ })()
+ this.ftsReady = true
+ } catch {
+ this.dropFtsIndex()
+ this.ftsReady = false
}
- this.ftsReady = true
}
insert(input: AgentMemoryInsertInput): AgentMemoryRow {
@@ -472,8 +537,11 @@ export class AgentMemoryTable extends BaseTable {
}
private searchFts(agentId: string, normalized: string, limit: number): AgentMemoryRow[] {
- // Quote the whole query as a phrase so FTS5 operators in user text can never break the MATCH.
- const match = `"${normalized.replace(/"/g, '""')}"`
+ const terms = tokenizeSearchQuery(normalized)
+ if (!terms.length) return []
+ // Quote each token so user text cannot inject FTS5 operators; join with AND so multi-word
+ // searches match memories containing all terms rather than requiring one exact phrase.
+ const match = terms.map((term) => `"${term.replace(/"/g, '""')}"`).join(' AND ')
try {
return this.db
.prepare(
@@ -496,7 +564,10 @@ export class AgentMemoryTable extends BaseTable {
}
private searchLike(agentId: string, normalized: string, limit: number): AgentMemoryRow[] {
- const pattern = `%${escapeLikePattern(normalized)}%`
+ const terms = tokenizeSearchQuery(normalized)
+ if (!terms.length) return []
+ const clauses = terms.map(() => "content LIKE ? ESCAPE '\\'")
+ const params = terms.map((term) => `%${escapeLikePattern(term)}%`)
return this.db
.prepare(
`SELECT * FROM agent_memory
@@ -505,11 +576,11 @@ export class AgentMemoryTable extends BaseTable {
AND status != 'archived'
AND status != 'conflicted'
AND kind != 'working'
- AND content LIKE ? ESCAPE '\\'
+ AND ${clauses.join(' AND ')}
ORDER BY importance DESC, created_at DESC
LIMIT ?`
)
- .all(agentId, pattern, limit) as AgentMemoryRow[]
+ .all(agentId, ...params, limit) as AgentMemoryRow[]
}
listPendingEmbedding(limit: number = 50, agentId?: string): AgentMemoryRow[] {
diff --git a/src/main/presenter/sqlitePresenter/tables/deepchatTapeSearchProjection.ts b/src/main/presenter/sqlitePresenter/tables/deepchatTapeSearchProjection.ts
index 554a5d072..ed4f05cd7 100644
--- a/src/main/presenter/sqlitePresenter/tables/deepchatTapeSearchProjection.ts
+++ b/src/main/presenter/sqlitePresenter/tables/deepchatTapeSearchProjection.ts
@@ -226,20 +226,24 @@ export class DeepChatTapeSearchProjectionTable extends BaseTable {
): void {
try {
this.db.transaction(() => {
+ if (this.ftsReady) {
+ this.db
+ .prepare('DELETE FROM deepchat_tape_search_fts WHERE session_id = ?')
+ .run(sessionId)
+ this.db
+ .prepare('DELETE FROM deepchat_tape_search_fts_meta WHERE session_id = ?')
+ .run(sessionId)
+ } else {
+ this.clearSessionFtsForBaseWrite(sessionId)
+ }
this.db
.prepare('DELETE FROM deepchat_tape_search_projection WHERE session_id = ?')
.run(sessionId)
this.db
.prepare('DELETE FROM deepchat_tape_search_projection_meta WHERE session_id = ?')
.run(sessionId)
- if (!this.ftsReady) {
- this.clearSessionFtsForBaseWrite(sessionId)
- }
this.insertProjectionRows(rows)
if (this.ftsReady) {
- this.db
- .prepare('DELETE FROM deepchat_tape_search_fts WHERE session_id = ?')
- .run(sessionId)
this.insertFtsRows(rows)
this.upsertFtsMeta(sessionId, projectionVersion, maxEntryId)
}
@@ -403,7 +407,9 @@ export class DeepChatTapeSearchProjectionTable extends BaseTable {
clearAll(): void {
this.db.prepare('DELETE FROM deepchat_tape_search_projection').run()
this.db.prepare('DELETE FROM deepchat_tape_search_projection_meta').run()
- this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta').run()
+ if (this.ftsMetaTableExists()) {
+ this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta').run()
+ }
this.clearFts()
}
@@ -499,17 +505,48 @@ export class DeepChatTapeSearchProjectionTable extends BaseTable {
return Boolean(row)
}
+ private ftsMetaTableExists(): boolean {
+ const row = this.db
+ .prepare(
+ `SELECT name
+ FROM sqlite_master
+ WHERE type = 'table' AND name = 'deepchat_tape_search_fts_meta'
+ LIMIT 1`
+ )
+ .get() as { name: string } | undefined
+ return Boolean(row)
+ }
+
private clearSessionFtsForBaseWrite(sessionId: string): void {
- this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta WHERE session_id = ?').run(sessionId)
+ if (this.ftsMetaTableExists()) {
+ this.db
+ .prepare('DELETE FROM deepchat_tape_search_fts_meta WHERE session_id = ?')
+ .run(sessionId)
+ }
if (this.ftsTableExists()) {
this.db.prepare('DELETE FROM deepchat_tape_search_fts WHERE session_id = ?').run(sessionId)
}
}
+ private getProjectionRowId(sessionId: string, entryId: number): number {
+ const row = this.db
+ .prepare(
+ `SELECT rowid
+ FROM deepchat_tape_search_projection
+ WHERE session_id = ? AND entry_id = ?`
+ )
+ .get(sessionId, entryId) as { rowid: number } | undefined
+ if (!row) {
+ throw new Error(`Missing tape search projection row for ${sessionId}:${entryId}`)
+ }
+ return row.rowid
+ }
+
private insertFtsRows(rows: DeepChatTapeSearchProjectionInput[]): void {
if (!rows.length) return
const insertFts = this.db.prepare(
`INSERT INTO deepchat_tape_search_fts (
+ rowid,
search_text,
name,
session_id,
@@ -522,13 +559,14 @@ export class DeepChatTapeSearchProjectionTable extends BaseTable {
refs_json,
created_at
)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
)
for (const row of rows) {
this.db
.prepare('DELETE FROM deepchat_tape_search_fts WHERE session_id = ? AND entry_id = ?')
.run(row.sessionId, row.entryId)
insertFts.run(
+ this.getProjectionRowId(row.sessionId, row.entryId),
row.searchText,
row.name ?? '',
row.sessionId,
@@ -601,12 +639,18 @@ export class DeepChatTapeSearchProjectionTable extends BaseTable {
dropFtsForTesting(): void {
this.db.exec('DROP TABLE IF EXISTS deepchat_tape_search_fts')
- this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta').run()
+ if (this.ftsMetaTableExists()) {
+ this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta').run()
+ }
this.ftsReady = false
}
private deleteSessionFts(sessionId: string): void {
- this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta WHERE session_id = ?').run(sessionId)
+ if (this.ftsMetaTableExists()) {
+ this.db
+ .prepare('DELETE FROM deepchat_tape_search_fts_meta WHERE session_id = ?')
+ .run(sessionId)
+ }
if (!this.ftsTableExists()) return
try {
this.db.prepare('DELETE FROM deepchat_tape_search_fts WHERE session_id = ?').run(sessionId)
@@ -616,7 +660,9 @@ export class DeepChatTapeSearchProjectionTable extends BaseTable {
}
private clearFts(): void {
- this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta').run()
+ if (this.ftsMetaTableExists()) {
+ this.db.prepare('DELETE FROM deepchat_tape_search_fts_meta').run()
+ }
if (!this.ftsTableExists()) return
try {
this.db.prepare('DELETE FROM deepchat_tape_search_fts').run()
@@ -658,9 +704,7 @@ export class DeepChatTapeSearchProjectionTable extends BaseTable {
bm25(deepchat_tape_search_fts) AS score
FROM deepchat_tape_search_fts
INNER JOIN deepchat_tape_search_projection AS projection
- ON projection.session_id = deepchat_tape_search_fts.session_id
- AND projection.entry_id = CAST(deepchat_tape_search_fts.entry_id AS INTEGER)
- AND projection.search_text = deepchat_tape_search_fts.search_text
+ ON projection.rowid = deepchat_tape_search_fts.rowid
WHERE ${whereClauses.join(' AND ')}
ORDER BY score ASC, projection.entry_id DESC
LIMIT ?`
diff --git a/src/renderer/settings/components/MemoryConfigPanel.vue b/src/renderer/settings/components/MemoryConfigPanel.vue
index 085aae0f0..52994155d 100644
--- a/src/renderer/settings/components/MemoryConfigPanel.vue
+++ b/src/renderer/settings/components/MemoryConfigPanel.vue
@@ -95,178 +95,239 @@
-
-
- {{ t('settings.memory.config.extractionModel') }}
-
-
-
-
-
+
@@ -259,15 +262,37 @@
>
-
-
-
+
+
+
+
+
+
+
+
+
+ {{ t('settings.deepchatAgents.memoryManager.deleteConfirmTitle') }}
+
+
+ {{ t('settings.deepchatAgents.memoryManager.deleteConfirmBody') }}
+
+
+
+ {{ t('common.cancel') }}
+
+ {{ t('settings.deepchatAgents.memoryManager.deletePermanent') }}
+
+
+
+
@@ -639,6 +664,7 @@ let searchTimer: ReturnType
| null = null
// Monotonic dispatch id: only the latest search may write results, so a late response from a
// superseded query (or a switched-away agent) cannot clobber the current one.
let searchRequestId = 0
+let refreshRequestId = 0
const conflicts = ref([])
const personaVersions = ref([])
const personaDrafts = ref([])
@@ -647,6 +673,7 @@ const viewManifests = ref([])
const status = ref(null)
const sourceSpanOpen = ref(false)
const sourceSpan = ref(null)
+const searchError = ref(null)
const hasEmbeddingConfigured = computed(() => props.hasEmbeddingConfigured === true)
// Only gates the write surface when the caller explicitly reports memory disabled; existing rows
@@ -679,6 +706,8 @@ async function refreshActivity(agentId: string): Promise {
async function refresh(): Promise {
if (!props.agentId) return
const agentId = props.agentId
+ refreshRequestId += 1
+ const requestId = refreshRequestId
loading.value = true
error.value = null
try {
@@ -689,12 +718,12 @@ async function refresh(): Promise {
memoryClient.listPersonaDrafts(agentId),
memoryClient.getStatus(agentId)
])
+ if (requestId !== refreshRequestId || props.agentId !== agentId) return
memories.value = list
conflicts.value = conflictPairs
personaVersions.value = versions
personaDrafts.value = drafts
status.value = currentStatus
- loading.value = false
// Reconcile the search cache with server truth so a mutation that reloads memories does not
// leave a stale (or already-deleted) row showing in search mode.
if (searchActive.value) {
@@ -703,8 +732,10 @@ async function refresh(): Promise {
}
void refreshActivity(agentId)
} catch (e) {
+ if (requestId !== refreshRequestId || props.agentId !== agentId) return
error.value = e instanceof Error ? e.message : String(e)
- loading.value = false
+ } finally {
+ if (requestId === refreshRequestId && props.agentId === agentId) loading.value = false
}
}
@@ -723,11 +754,16 @@ function isCurrentSearch(agentId: string, query: string, requestId: number): boo
async function runSearch(agentId: string, query: string, requestId: number): Promise {
searching.value = true
+ searchError.value = null
try {
const results = await memoryClient.search(agentId, query)
if (isCurrentSearch(agentId, query, requestId)) searchResults.value = results
- } catch {
- if (isCurrentSearch(agentId, query, requestId)) searchResults.value = []
+ } catch (e) {
+ if (isCurrentSearch(agentId, query, requestId)) {
+ searchResults.value = []
+ searchError.value =
+ e instanceof Error ? e.message : t('settings.deepchatAgents.memoryManager.searchFailed')
+ }
} finally {
if (isCurrentSearch(agentId, query, requestId)) searching.value = false
}
@@ -739,6 +775,7 @@ function resetSearch(): void {
searchRequestId += 1
searchQuery.value = ''
searchResults.value = []
+ searchError.value = null
searching.value = false
}
@@ -751,6 +788,7 @@ watch(searchQuery, (value) => {
const requestId = searchRequestId
if (!query) {
searchResults.value = []
+ searchError.value = null
searching.value = false
return
}
@@ -852,8 +890,7 @@ async function handleDelete(memoryId: string): Promise {
try {
const ok = await memoryClient.remove(props.agentId, memoryId)
if (!ok) return notifyActionFailed()
- memories.value = memories.value.filter((memory) => memory.id !== memoryId)
- searchResults.value = searchResults.value.filter((memory) => memory.id !== memoryId)
+ await refresh()
} catch (e) {
notifyActionFailed(e)
}
diff --git a/src/renderer/settings/components/MemorySettings.vue b/src/renderer/settings/components/MemorySettings.vue
index 27960da82..a675bb02c 100644
--- a/src/renderer/settings/components/MemorySettings.vue
+++ b/src/renderer/settings/components/MemorySettings.vue
@@ -9,6 +9,16 @@
{{ t('common.loading') }}
+
+
{{ loadError }}
+
reload()">
+ {{ t('common.reset') }}
+
+
+
('config')
const resolvedSelected = ref(null)
const resolvedAgentId = ref('')
+const loadError = ref(null)
// Resolved config describes the selected agent only once its own resolve has landed. Mid-switch the
// manager panel remounts on the new agentId immediately, so these flags must not leak the previous
@@ -165,9 +177,24 @@ function onSelect(value: unknown): void {
void router.replace({ query: { ...route.query, agentId: id } })
}
+async function reload(preferred?: string | null): Promise {
+ loading.value = true
+ loadError.value = null
+ try {
+ await loadAgents(preferred ?? selectedAgentId.value)
+ await loadResolved()
+ } catch (e) {
+ agents.value = []
+ resolvedSelected.value = null
+ resolvedAgentId.value = ''
+ loadError.value = e instanceof Error ? e.message : String(e)
+ } finally {
+ loading.value = false
+ }
+}
+
async function onSaved(): Promise {
- await loadAgents(selectedAgentId.value)
- await loadResolved()
+ await reload(selectedAgentId.value)
}
watch(selectedAgentId, loadResolved)
@@ -180,10 +207,8 @@ watch(
}
)
-onMounted(async () => {
+onMounted(() => {
const fromQuery = typeof route.query.agentId === 'string' ? route.query.agentId : null
- await loadAgents(fromQuery)
- await loadResolved()
- loading.value = false
+ void reload(fromQuery)
})
diff --git a/src/renderer/src/i18n/da-DK/settings.json b/src/renderer/src/i18n/da-DK/settings.json
index 53b996e45..7004c80d1 100644
--- a/src/renderer/src/i18n/da-DK/settings.json
+++ b/src/renderer/src/i18n/da-DK/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "Tilføjet, men er i konflikt med et eksisterende minde — løs det i listen.",
"addDuplicate": "Et lignende minde findes allerede; intet blev tilføjet.",
"addDisabledHint": "Hukommelse er slået fra for denne assistent. Aktivér den i Konfiguration, før du tilføjer minder.",
- "addSkipped": "Intet minde blev tilføjet."
+ "addSkipped": "Intet minde blev tilføjet.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Personaudvikling (eksperimentel)",
"personaEvolutionDescription": "Lad refleksion foreslå opdaterede selvmodeller som kladder til din godkendelse. Uafhængig af hukommelse; slået fra som standard.",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "Lighedstærskel",
"weightSimilarity": "Vægt · lighed",
"weightRecency": "Vægt · aktualitet",
- "weightImportance": "Vægt · vigtighed"
+ "weightImportance": "Vægt · vigtighed",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/de-DE/settings.json b/src/renderer/src/i18n/de-DE/settings.json
index 4a6f4d33d..5050713dd 100644
--- a/src/renderer/src/i18n/de-DE/settings.json
+++ b/src/renderer/src/i18n/de-DE/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Hinzugefügt, steht aber im Konflikt mit einer vorhandenen Erinnerung – bitte in der Liste klären.",
"addDuplicate": "Eine ähnliche Erinnerung existiert bereits; nichts wurde hinzugefügt.",
"addDisabledHint": "Das Gedächtnis ist für diesen Assistenten deaktiviert. Aktivieren Sie es zuerst in der Konfiguration, um Erinnerungen hinzuzufügen.",
- "addSkipped": "Es wurde keine Erinnerung hinzugefügt."
+ "addSkipped": "Es wurde keine Erinnerung hinzugefügt.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Persona-Entwicklung (experimentell)",
"personaEvolutionDescription": "Lassen Sie die Reflexion aktualisierte Selbstmodelle als Entwürfe zur Freigabe vorschlagen. Unabhängig vom Speicher; standardmäßig deaktiviert.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Ähnlichkeitsschwelle",
"weightSimilarity": "Gewicht · Ähnlichkeit",
"weightRecency": "Gewicht · Aktualität",
- "weightImportance": "Gewicht · Wichtigkeit"
+ "weightImportance": "Gewicht · Wichtigkeit",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/en-US/settings.json b/src/renderer/src/i18n/en-US/settings.json
index ff37a7e78..26e6041d3 100644
--- a/src/renderer/src/i18n/en-US/settings.json
+++ b/src/renderer/src/i18n/en-US/settings.json
@@ -218,7 +218,11 @@
"addConflict": "Added, but it conflicts with an existing memory — resolve it in the list.",
"addDuplicate": "A similar memory already exists; nothing was added.",
"addDisabledHint": "Memory is off for this assistant. Enable it in Config before adding memories.",
- "addSkipped": "No memory was added."
+ "addSkipped": "No memory was added.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"compactionThreshold": "Trigger threshold",
"compactionRetainPairs": "Retain recent pairs",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Similarity threshold",
"weightSimilarity": "Weight · similarity",
"weightRecency": "Weight · recency",
- "weightImportance": "Weight · importance"
+ "weightImportance": "Weight · importance",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/es-ES/settings.json b/src/renderer/src/i18n/es-ES/settings.json
index ba93a0f70..1000d60b6 100644
--- a/src/renderer/src/i18n/es-ES/settings.json
+++ b/src/renderer/src/i18n/es-ES/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Añadida, pero entra en conflicto con una memoria existente; resuélvelo en la lista.",
"addDuplicate": "Ya existe una memoria similar; no se añadió nada.",
"addDisabledHint": "La memoria está desactivada para este asistente. Actívala en Configuración antes de añadir memorias.",
- "addSkipped": "No se añadió ninguna memoria."
+ "addSkipped": "No se añadió ninguna memoria.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Evolución de la persona (experimental)",
"personaEvolutionDescription": "Permite que la reflexión proponga modelos de sí mismo actualizados como borradores para tu aprobación. Independiente de la memoria; desactivado por defecto.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Umbral de similitud",
"weightSimilarity": "Peso · similitud",
"weightRecency": "Peso · actualidad",
- "weightImportance": "Peso · importancia"
+ "weightImportance": "Peso · importancia",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/fa-IR/settings.json b/src/renderer/src/i18n/fa-IR/settings.json
index 319c47972..1382fd18f 100644
--- a/src/renderer/src/i18n/fa-IR/settings.json
+++ b/src/renderer/src/i18n/fa-IR/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "افزوده شد، اما با یک حافظهٔ موجود تداخل دارد — آن را در فهرست حل کنید.",
"addDuplicate": "حافظهٔ مشابهی از قبل وجود دارد؛ چیزی افزوده نشد.",
"addDisabledHint": "حافظه برای این دستیار غیرفعال است. پیش از افزودن حافظه، آن را در «پیکربندی» فعال کنید.",
- "addSkipped": "هیچ حافظهای افزوده نشد."
+ "addSkipped": "هیچ حافظهای افزوده نشد.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "تکامل پرسونا (آزمایشی)",
"personaEvolutionDescription": "اجازه دهید بازتاب، مدلهای خودِ بهروزشده را بهصورت پیشنویس برای تأیید شما پیشنهاد دهد. مستقل از حافظه؛ بهطور پیشفرض خاموش.",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "آستانهٔ شباهت",
"weightSimilarity": "وزن · شباهت",
"weightRecency": "وزن · تازگی",
- "weightImportance": "وزن · اهمیت"
+ "weightImportance": "وزن · اهمیت",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/fr-FR/settings.json b/src/renderer/src/i18n/fr-FR/settings.json
index b493abc41..99ca11df9 100644
--- a/src/renderer/src/i18n/fr-FR/settings.json
+++ b/src/renderer/src/i18n/fr-FR/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "Ajoutée, mais en conflit avec une mémoire existante — résolvez-le dans la liste.",
"addDuplicate": "Une mémoire similaire existe déjà ; rien n'a été ajouté.",
"addDisabledHint": "La mémoire est désactivée pour cet assistant. Activez-la dans la configuration avant d'ajouter des mémoires.",
- "addSkipped": "Aucune mémoire n'a été ajoutée."
+ "addSkipped": "Aucune mémoire n'a été ajoutée.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Évolution de la persona (expérimental)",
"personaEvolutionDescription": "Laissez la réflexion proposer des modèles de soi mis à jour sous forme de brouillons à approuver. Indépendant de la mémoire ; désactivé par défaut.",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "Seuil de similarité",
"weightSimilarity": "Poids · similarité",
"weightRecency": "Poids · récence",
- "weightImportance": "Poids · importance"
+ "weightImportance": "Poids · importance",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/he-IL/settings.json b/src/renderer/src/i18n/he-IL/settings.json
index 896e58c27..bb0f60a39 100644
--- a/src/renderer/src/i18n/he-IL/settings.json
+++ b/src/renderer/src/i18n/he-IL/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "נוסף, אך מתנגש עם זיכרון קיים — פתור זאת ברשימה.",
"addDuplicate": "כבר קיים זיכרון דומה; לא נוסף דבר.",
"addDisabledHint": "הזיכרון כבוי עבור עוזר זה. הפעל אותו בהגדרות לפני הוספת זיכרונות.",
- "addSkipped": "לא נוסף זיכרון."
+ "addSkipped": "לא נוסף זיכרון.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "התפתחות פרסונה (ניסיוני)",
"personaEvolutionDescription": "אפשר לרפלקציה להציע מודלים עצמיים מעודכנים כטיוטות לאישורך. בלתי תלוי בזיכרון; כבוי כברירת מחדל.",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "סף דמיון",
"weightSimilarity": "משקל · דמיון",
"weightRecency": "משקל · עדכניות",
- "weightImportance": "משקל · חשיבות"
+ "weightImportance": "משקל · חשיבות",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/id-ID/settings.json b/src/renderer/src/i18n/id-ID/settings.json
index 0ccf58249..bd024376d 100644
--- a/src/renderer/src/i18n/id-ID/settings.json
+++ b/src/renderer/src/i18n/id-ID/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Ditambahkan, tetapi bertentangan dengan memori yang ada — selesaikan di daftar.",
"addDuplicate": "Memori serupa sudah ada; tidak ada yang ditambahkan.",
"addDisabledHint": "Memori dinonaktifkan untuk asisten ini. Aktifkan di Konfigurasi sebelum menambahkan memori.",
- "addSkipped": "Tidak ada memori yang ditambahkan."
+ "addSkipped": "Tidak ada memori yang ditambahkan.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Evolusi persona (eksperimental)",
"personaEvolutionDescription": "Biarkan refleksi mengusulkan model diri yang diperbarui sebagai draf untuk persetujuan Anda. Independen dari memori; nonaktif secara default.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Ambang kemiripan",
"weightSimilarity": "Bobot · kemiripan",
"weightRecency": "Bobot · kebaruan",
- "weightImportance": "Bobot · kepentingan"
+ "weightImportance": "Bobot · kepentingan",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/it-IT/settings.json b/src/renderer/src/i18n/it-IT/settings.json
index 57fea8d70..c10f988db 100644
--- a/src/renderer/src/i18n/it-IT/settings.json
+++ b/src/renderer/src/i18n/it-IT/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Aggiunta, ma è in conflitto con una memoria esistente: risolvilo nell'elenco.",
"addDuplicate": "Esiste già una memoria simile; non è stato aggiunto nulla.",
"addDisabledHint": "La memoria è disattivata per questo assistente. Attivala nella configurazione prima di aggiungere memorie.",
- "addSkipped": "Nessuna memoria è stata aggiunta."
+ "addSkipped": "Nessuna memoria è stata aggiunta.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Evoluzione della persona (sperimentale)",
"personaEvolutionDescription": "Lascia che la riflessione proponga modelli di sé aggiornati come bozze da approvare. Indipendente dalla memoria; disattivata per impostazione predefinita.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Soglia di similarità",
"weightSimilarity": "Peso · similarità",
"weightRecency": "Peso · recency",
- "weightImportance": "Peso · importanza"
+ "weightImportance": "Peso · importanza",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/ja-JP/settings.json b/src/renderer/src/i18n/ja-JP/settings.json
index 78eba6c11..6f6d78b29 100644
--- a/src/renderer/src/i18n/ja-JP/settings.json
+++ b/src/renderer/src/i18n/ja-JP/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "追加しましたが、既存のメモリと競合しています。リストで解決してください。",
"addDuplicate": "類似のメモリが既に存在するため、追加されませんでした。",
"addDisabledHint": "このアシスタントのメモリは無効です。メモリを追加する前に「設定」で有効にしてください。",
- "addSkipped": "メモリは追加されませんでした。"
+ "addSkipped": "メモリは追加されませんでした。",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "人格進化(実験的)",
"personaEvolutionDescription": "リフレクションが更新後の自己モデルを下書きとして提案し、あなたの承認を待ちます。メモリとは独立で、既定では無効です。",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "類似度しきい値",
"weightSimilarity": "重み · 類似度",
"weightRecency": "重み · 新しさ",
- "weightImportance": "重み · 重要度"
+ "weightImportance": "重み · 重要度",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/ko-KR/settings.json b/src/renderer/src/i18n/ko-KR/settings.json
index 99e175f73..c4ab4cb55 100644
--- a/src/renderer/src/i18n/ko-KR/settings.json
+++ b/src/renderer/src/i18n/ko-KR/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "추가했지만 기존 메모리와 충돌합니다. 목록에서 해결하세요.",
"addDuplicate": "유사한 메모리가 이미 있어 추가하지 않았습니다.",
"addDisabledHint": "이 어시스턴트의 메모리가 꺼져 있습니다. 메모리를 추가하려면 먼저 구성에서 활성화하세요.",
- "addSkipped": "메모리가 추가되지 않았습니다."
+ "addSkipped": "메모리가 추가되지 않았습니다.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "페르소나 진화 (실험적)",
"personaEvolutionDescription": "리플렉션이 업데이트된 자기 모델을 초안으로 제안하여 승인을 받도록 합니다. 메모리와 독립적이며 기본값은 꺼짐입니다.",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "유사도 임계값",
"weightSimilarity": "가중치 · 유사도",
"weightRecency": "가중치 · 최신성",
- "weightImportance": "가중치 · 중요도"
+ "weightImportance": "가중치 · 중요도",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/ms-MY/settings.json b/src/renderer/src/i18n/ms-MY/settings.json
index e27cc5278..df42f1ffc 100644
--- a/src/renderer/src/i18n/ms-MY/settings.json
+++ b/src/renderer/src/i18n/ms-MY/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Ditambah, tetapi bercanggah dengan ingatan sedia ada — selesaikannya dalam senarai.",
"addDuplicate": "Ingatan serupa sudah wujud; tiada apa-apa ditambah.",
"addDisabledHint": "Ingatan dimatikan untuk pembantu ini. Aktifkannya dalam Konfigurasi sebelum menambah ingatan.",
- "addSkipped": "Tiada ingatan ditambah."
+ "addSkipped": "Tiada ingatan ditambah.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Evolusi persona (eksperimental)",
"personaEvolutionDescription": "Benarkan refleksi mencadangkan model diri yang dikemas kini sebagai draf untuk kelulusan anda. Bebas daripada memori; dimatikan secara lalai.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Ambang persamaan",
"weightSimilarity": "Berat · persamaan",
"weightRecency": "Berat · kebaharuan",
- "weightImportance": "Berat · kepentingan"
+ "weightImportance": "Berat · kepentingan",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/pl-PL/settings.json b/src/renderer/src/i18n/pl-PL/settings.json
index 413867ef7..8d103f135 100644
--- a/src/renderer/src/i18n/pl-PL/settings.json
+++ b/src/renderer/src/i18n/pl-PL/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Dodano, ale jest sprzeczne z istniejącym wspomnieniem — rozwiąż to na liście.",
"addDuplicate": "Podobne wspomnienie już istnieje; nic nie dodano.",
"addDisabledHint": "Pamięć jest wyłączona dla tego asystenta. Włącz ją w Konfiguracji, zanim dodasz wspomnienia.",
- "addSkipped": "Nie dodano żadnego wspomnienia."
+ "addSkipped": "Nie dodano żadnego wspomnienia.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Ewolucja persony (eksperymentalna)",
"personaEvolutionDescription": "Pozwól, aby refleksja proponowała zaktualizowane modele siebie jako wersje robocze do zatwierdzenia. Niezależna od pamięci; domyślnie wyłączona.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Próg podobieństwa",
"weightSimilarity": "Waga · podobieństwo",
"weightRecency": "Waga · aktualność",
- "weightImportance": "Waga · ważność"
+ "weightImportance": "Waga · ważność",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/pt-BR/settings.json b/src/renderer/src/i18n/pt-BR/settings.json
index d1f069528..df1bb3c1e 100644
--- a/src/renderer/src/i18n/pt-BR/settings.json
+++ b/src/renderer/src/i18n/pt-BR/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "Adicionada, mas entra em conflito com uma memória existente — resolva na lista.",
"addDuplicate": "Já existe uma memória semelhante; nada foi adicionado.",
"addDisabledHint": "A memória está desativada para este assistente. Ative-a na Configuração antes de adicionar memórias.",
- "addSkipped": "Nenhuma memória foi adicionada."
+ "addSkipped": "Nenhuma memória foi adicionada.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Evolução da persona (experimental)",
"personaEvolutionDescription": "Permita que a reflexão proponha modelos de si atualizados como rascunhos para sua aprovação. Independente da memória; desativado por padrão.",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "Limiar de similaridade",
"weightSimilarity": "Peso · similaridade",
"weightRecency": "Peso · recência",
- "weightImportance": "Peso · importância"
+ "weightImportance": "Peso · importância",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/ru-RU/settings.json b/src/renderer/src/i18n/ru-RU/settings.json
index ec49dbc3d..1cbe8b15a 100644
--- a/src/renderer/src/i18n/ru-RU/settings.json
+++ b/src/renderer/src/i18n/ru-RU/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "Добавлено, но конфликтует с существующим воспоминанием — разрешите это в списке.",
"addDuplicate": "Похожее воспоминание уже есть; ничего не добавлено.",
"addDisabledHint": "Память отключена для этого ассистента. Включите её в настройках, прежде чем добавлять воспоминания.",
- "addSkipped": "Воспоминание не добавлено."
+ "addSkipped": "Воспоминание не добавлено.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Эволюция персоны (эксперимент)",
"personaEvolutionDescription": "Позвольте рефлексии предлагать обновлённые модели себя в виде черновиков для вашего одобрения. Независимо от памяти; по умолчанию выключено.",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "Порог сходства",
"weightSimilarity": "Вес · сходство",
"weightRecency": "Вес · давность",
- "weightImportance": "Вес · важность"
+ "weightImportance": "Вес · важность",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/tr-TR/settings.json b/src/renderer/src/i18n/tr-TR/settings.json
index 943d24f0e..3b97ab37d 100644
--- a/src/renderer/src/i18n/tr-TR/settings.json
+++ b/src/renderer/src/i18n/tr-TR/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Eklendi ancak mevcut bir anıyla çakışıyor — listede çözün.",
"addDuplicate": "Benzer bir anı zaten var; hiçbir şey eklenmedi.",
"addDisabledHint": "Bu asistan için bellek kapalı. Anı eklemeden önce Yapılandırma'dan etkinleştirin.",
- "addSkipped": "Hiçbir anı eklenmedi."
+ "addSkipped": "Hiçbir anı eklenmedi.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Kişilik evrimi (deneysel)",
"personaEvolutionDescription": "Yansımanın güncellenmiş benlik modellerini onayınız için taslak olarak önermesine izin verin. Bellekten bağımsızdır; varsayılan olarak kapalıdır.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Benzerlik eşiği",
"weightSimilarity": "Ağırlık · benzerlik",
"weightRecency": "Ağırlık · güncellik",
- "weightImportance": "Ağırlık · önem"
+ "weightImportance": "Ağırlık · önem",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/vi-VN/settings.json b/src/renderer/src/i18n/vi-VN/settings.json
index 3602ca5a7..c1afb03d5 100644
--- a/src/renderer/src/i18n/vi-VN/settings.json
+++ b/src/renderer/src/i18n/vi-VN/settings.json
@@ -221,7 +221,11 @@
"addConflict": "Đã thêm, nhưng xung đột với một ký ức hiện có — hãy giải quyết trong danh sách.",
"addDuplicate": "Đã có một ký ức tương tự; không có gì được thêm.",
"addDisabledHint": "Bộ nhớ đã tắt cho trợ lý này. Hãy bật trong phần Cấu hình trước khi thêm ký ức.",
- "addSkipped": "Không có ký ức nào được thêm."
+ "addSkipped": "Không có ký ức nào được thêm.",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "Tiến hóa tính cách (thử nghiệm)",
"personaEvolutionDescription": "Cho phép quá trình phản tư đề xuất các mô hình bản thân cập nhật dưới dạng bản nháp để bạn phê duyệt. Độc lập với bộ nhớ; mặc định tắt.",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "Ngưỡng tương đồng",
"weightSimilarity": "Trọng số · tương đồng",
"weightRecency": "Trọng số · gần đây",
- "weightImportance": "Trọng số · quan trọng"
+ "weightImportance": "Trọng số · quan trọng",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/zh-CN/settings.json b/src/renderer/src/i18n/zh-CN/settings.json
index 08602c354..5695ba6dd 100644
--- a/src/renderer/src/i18n/zh-CN/settings.json
+++ b/src/renderer/src/i18n/zh-CN/settings.json
@@ -218,7 +218,11 @@
"addConflict": "已添加,但与现有记忆冲突,请在列表中裁决。",
"addDuplicate": "已记住类似内容,未重复添加。",
"addDisabledHint": "该助手的记忆已关闭,请先在「配置」中启用后再添加记忆。",
- "addSkipped": "未添加记忆。"
+ "addSkipped": "未添加记忆。",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"compactionThreshold": "触发阈值",
"compactionRetainPairs": "保留最近消息对",
@@ -2413,7 +2417,14 @@
"similarityThreshold": "相似度阈值",
"weightSimilarity": "权重 · 相似度",
"weightRecency": "权重 · 时近度",
- "weightImportance": "权重 · 重要度"
+ "weightImportance": "权重 · 重要度",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/zh-HK/settings.json b/src/renderer/src/i18n/zh-HK/settings.json
index 9a00687c2..ad1d88a87 100644
--- a/src/renderer/src/i18n/zh-HK/settings.json
+++ b/src/renderer/src/i18n/zh-HK/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "已新增,但與現有記憶衝突,請在清單中裁決。",
"addDuplicate": "已記住類似內容,未重複新增。",
"addDisabledHint": "此助手的記憶已關閉,請先在「配置」中啟用後再新增記憶。",
- "addSkipped": "未新增記憶。"
+ "addSkipped": "未新增記憶。",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "人格演化(實驗)",
"personaEvolutionDescription": "讓反思以草稿形式提出更新後的自我模型,交由你審核。獨立於記憶開關,預設關閉。",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "相似度閾值",
"weightSimilarity": "權重 · 相似度",
"weightRecency": "權重 · 時近度",
- "weightImportance": "權重 · 重要度"
+ "weightImportance": "權重 · 重要度",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/zh-TW/settings.json b/src/renderer/src/i18n/zh-TW/settings.json
index 0a19a3642..76815dd31 100644
--- a/src/renderer/src/i18n/zh-TW/settings.json
+++ b/src/renderer/src/i18n/zh-TW/settings.json
@@ -2290,7 +2290,11 @@
"addConflict": "已新增,但與現有記憶衝突,請在清單中裁決。",
"addDuplicate": "已記住類似內容,未重複新增。",
"addDisabledHint": "此助手的記憶已關閉,請先在「配置」中啟用後再新增記憶。",
- "addSkipped": "未新增記憶。"
+ "addSkipped": "未新增記憶。",
+ "searchFailed": "Search failed. Try again.",
+ "deletePermanent": "Delete permanently",
+ "deleteConfirmTitle": "Delete this memory permanently?",
+ "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
},
"personaEvolutionTitle": "人格演化(實驗)",
"personaEvolutionDescription": "讓反思以草稿形式提出更新後的自我模型,交由你審核。獨立於記憶開關,預設關閉。",
@@ -2422,7 +2426,14 @@
"similarityThreshold": "相似度閾值",
"weightSimilarity": "權重 · 相似度",
"weightRecency": "權重 · 時近度",
- "weightImportance": "權重 · 重要度"
+ "weightImportance": "權重 · 重要度",
+ "advancedTitle": "Advanced memory settings",
+ "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
+ "inheritedHint": "Empty uses inherited/default value.",
+ "topKHint": "Range 1-100. Default: {default}.",
+ "rrfKHint": "Range 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Range 0-1. Default: {default}.",
+ "weightHint": "Non-negative ranking weight. Default: {default}."
}
}
}
diff --git a/test/main/presenter/memoryExtraction.test.ts b/test/main/presenter/memoryExtraction.test.ts
index 9e49726d5..47105e5b4 100644
--- a/test/main/presenter/memoryExtraction.test.ts
+++ b/test/main/presenter/memoryExtraction.test.ts
@@ -33,43 +33,58 @@ describe('parseMemoryCandidates', () => {
const out = parseMemoryCandidates(
'[{"kind":"semantic","content":"user likes redis","importance":0.8}]'
)
- expect(out).toEqual([{ kind: 'semantic', content: 'user likes redis', importance: 0.8 }])
+ expect(out).toEqual({
+ ok: true,
+ candidates: [{ kind: 'semantic', content: 'user likes redis', importance: 0.8 }]
+ })
})
it('parses JSON inside ```json fences with surrounding prose', () => {
const raw = 'Here you go:\n```json\n[{"kind":"episodic","content":"shipped v1"}]\n```\nDone.'
const out = parseMemoryCandidates(raw)
- expect(out).toHaveLength(1)
- expect(out[0]).toMatchObject({ kind: 'episodic', content: 'shipped v1' })
- expect(out[0].importance).toBe(0.5) // default
+ expect(out.ok).toBe(true)
+ if (!out.ok) throw new Error('expected parse to succeed')
+ expect(out.candidates).toHaveLength(1)
+ expect(out.candidates[0]).toMatchObject({ kind: 'episodic', content: 'shipped v1' })
+ expect(out.candidates[0].importance).toBe(0.5) // default
})
it('defaults kind to semantic and clamps importance', () => {
const out = parseMemoryCandidates(
'[{"content":"x","importance":5},{"content":"y","importance":-2}]'
)
- expect(out[0]).toMatchObject({ kind: 'semantic', importance: 1 })
- expect(out[1]).toMatchObject({ kind: 'semantic', importance: 0 })
+ expect(out.ok).toBe(true)
+ if (!out.ok) throw new Error('expected parse to succeed')
+ expect(out.candidates[0]).toMatchObject({ kind: 'semantic', importance: 1 })
+ expect(out.candidates[1]).toMatchObject({ kind: 'semantic', importance: 0 })
})
it('drops entries without content', () => {
const out = parseMemoryCandidates('[{"kind":"semantic"},{"content":" "},{"content":"ok"}]')
- expect(out).toHaveLength(1)
- expect(out[0].content).toBe('ok')
+ expect(out.ok).toBe(true)
+ if (!out.ok) throw new Error('expected parse to succeed')
+ expect(out.candidates).toHaveLength(1)
+ expect(out.candidates[0].content).toBe('ok')
})
- it('returns [] for empty / non-array / garbage', () => {
- expect(parseMemoryCandidates('')).toEqual([])
- expect(parseMemoryCandidates('not json')).toEqual([])
- expect(parseMemoryCandidates('{"content":"x"}')).toEqual([])
- expect(parseMemoryCandidates('[broken')).toEqual([])
+ it('returns parse failures for empty / non-array / garbage', () => {
+ expect(parseMemoryCandidates('')).toEqual({ ok: false, reason: 'empty-response' })
+ expect(parseMemoryCandidates('not json')).toEqual({ ok: false, reason: 'missing-json-array' })
+ expect(parseMemoryCandidates('{"content":"x"}')).toEqual({
+ ok: false,
+ reason: 'missing-json-array'
+ })
+ expect(parseMemoryCandidates('[broken')).toEqual({ ok: false, reason: 'missing-json-array' })
})
it('caps at 8 candidates', () => {
const many = JSON.stringify(
Array.from({ length: 20 }, (_, i) => ({ kind: 'semantic', content: `c${i}` }))
)
- expect(parseMemoryCandidates(many)).toHaveLength(8)
+ const out = parseMemoryCandidates(many)
+ expect(out.ok).toBe(true)
+ if (!out.ok) throw new Error('expected parse to succeed')
+ expect(out.candidates).toHaveLength(8)
})
})
From aff73fcc445007004c7c5b86caa96f9533b580c1 Mon Sep 17 00:00:00 2001
From: zhangmo8
Date: Mon, 22 Jun 2026 11:31:10 +0800
Subject: [PATCH 2/3] chore: update
---
docs/issues/pr1794-memory-hardening/plan.md | 1 +
docs/issues/pr1794-memory-hardening/spec.md | 2 +-
docs/issues/pr1794-memory-hardening/tasks.md | 2 +-
src/main/routes/index.ts | 5 -----
src/renderer/src/i18n/da-DK/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/de-DE/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/es-ES/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/fa-IR/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/fr-FR/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/he-IL/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/id-ID/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/it-IT/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/ja-JP/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/ko-KR/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/ms-MY/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/pl-PL/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/pt-BR/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/ru-RU/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/tr-TR/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/vi-VN/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/zh-CN/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/zh-HK/settings.json | 22 ++++++++++----------
src/renderer/src/i18n/zh-TW/settings.json | 22 ++++++++++----------
test/main/routes/memoryDto.test.ts | 3 ++-
24 files changed, 214 insertions(+), 217 deletions(-)
diff --git a/docs/issues/pr1794-memory-hardening/plan.md b/docs/issues/pr1794-memory-hardening/plan.md
index 39a4389cd..9cb196a6e 100644
--- a/docs/issues/pr1794-memory-hardening/plan.md
+++ b/docs/issues/pr1794-memory-hardening/plan.md
@@ -23,6 +23,7 @@
3. Surface search errors distinctly from empty results.
4. Rename default destructive memory operations to archive/restore semantics; keep permanent delete as an explicit dangerous action if still exposed.
5. Move/label advanced retrieval tuning to reduce accidental misuse.
+6. Localize all newly added memory management and advanced settings copy in every supported locale, preserving interpolation placeholders.
## Tests
diff --git a/docs/issues/pr1794-memory-hardening/spec.md b/docs/issues/pr1794-memory-hardening/spec.md
index 6e1d2735d..932b169e0 100644
--- a/docs/issues/pr1794-memory-hardening/spec.md
+++ b/docs/issues/pr1794-memory-hardening/spec.md
@@ -28,7 +28,7 @@ Fix the must-fix and medium-high priority review findings for PR #1794 while pre
- Preserve backward compatibility with existing local SQLite databases.
- Do not introduce new runtime dependencies.
- Main-process DB operations remain synchronous where existing SQLite presenter patterns require it.
-- UI strings must use i18n keys.
+- UI strings must use i18n keys and newly added translations must be localized for each supported locale rather than copied from English.
## Non-goals
diff --git a/docs/issues/pr1794-memory-hardening/tasks.md b/docs/issues/pr1794-memory-hardening/tasks.md
index 84af6b236..bd516e276 100644
--- a/docs/issues/pr1794-memory-hardening/tasks.md
+++ b/docs/issues/pr1794-memory-hardening/tasks.md
@@ -11,6 +11,6 @@
- [x] DB: harden clear/reset and new DB schema version initialization.
- [x] UI: add MemorySettings error handling.
- [x] UI: guard MemoryManagerPanel refresh and search errors.
-- [x] UI: clarify archive vs permanent delete and advanced settings.
+- [x] UI: localize new memory management and advanced settings strings for all supported locales.
- [x] Tests: update memory extraction tests for `parseMemoryCandidates` union return.
- [ ] Validation: `pnpm run format`, `pnpm run i18n`, `pnpm run lint`, and `pnpm run typecheck` passed under Node v26 warning; `pnpm test` was stopped per user instruction after memoryPresenter failures surfaced.
diff --git a/src/main/routes/index.ts b/src/main/routes/index.ts
index 586804ba2..130819672 100644
--- a/src/main/routes/index.ts
+++ b/src/main/routes/index.ts
@@ -422,13 +422,8 @@ export function formatMemorySourceRecordContent(record: ChatMessageRecord): stri
const b = block as {
type?: string
content?: unknown
- reasoning_content?: unknown
- text?: unknown
}
if (b?.type === 'content' && typeof b.content === 'string') return b.content
- if (b?.type === 'reasoning_content' && typeof b.content === 'string') return b.content
- if (typeof b?.reasoning_content === 'string') return b.reasoning_content
- if (b?.type === 'reasoning' && typeof b.text === 'string') return b.text
return ''
}
if (Array.isArray(parsed)) {
diff --git a/src/renderer/src/i18n/da-DK/settings.json b/src/renderer/src/i18n/da-DK/settings.json
index 7004c80d1..54c4e4020 100644
--- a/src/renderer/src/i18n/da-DK/settings.json
+++ b/src/renderer/src/i18n/da-DK/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "Et lignende minde findes allerede; intet blev tilføjet.",
"addDisabledHint": "Hukommelse er slået fra for denne assistent. Aktivér den i Konfiguration, før du tilføjer minder.",
"addSkipped": "Intet minde blev tilføjet.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Søgningen mislykkedes. Prøv igen.",
+ "deletePermanent": "Slet permanent",
+ "deleteConfirmTitle": "Slet denne hukommelse permanent?",
+ "deleteConfirmBody": "Dette fjerner hukommelsen i stedet for at arkivere den. Handlingen kan ikke fortrydes."
},
"personaEvolutionTitle": "Personaudvikling (eksperimentel)",
"personaEvolutionDescription": "Lad refleksion foreslå opdaterede selvmodeller som kladder til din godkendelse. Uafhængig af hukommelse; slået fra som standard.",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "Vægt · lighed",
"weightRecency": "Vægt · aktualitet",
"weightImportance": "Vægt · vigtighed",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Avancerede hukommelsesindstillinger",
+ "advancedHint": "Juster udtrækning, promptbudget og rangering ved genfinding. Lad felter være tomme for at arve standardværdier.",
+ "inheritedHint": "Tomt felt bruger arvet/standardværdi.",
+ "topKHint": "Interval 1-100. Standard: {default}.",
+ "rrfKHint": "Interval 1-1000. Standard: {default}.",
+ "similarityThresholdHint": "Interval 0-1. Standard: {default}.",
+ "weightHint": "Ikke-negativ rangeringsvægt. Standard: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/de-DE/settings.json b/src/renderer/src/i18n/de-DE/settings.json
index 5050713dd..6d94ff024 100644
--- a/src/renderer/src/i18n/de-DE/settings.json
+++ b/src/renderer/src/i18n/de-DE/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Eine ähnliche Erinnerung existiert bereits; nichts wurde hinzugefügt.",
"addDisabledHint": "Das Gedächtnis ist für diesen Assistenten deaktiviert. Aktivieren Sie es zuerst in der Konfiguration, um Erinnerungen hinzuzufügen.",
"addSkipped": "Es wurde keine Erinnerung hinzugefügt.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Suche fehlgeschlagen. Bitte erneut versuchen.",
+ "deletePermanent": "Endgültig löschen",
+ "deleteConfirmTitle": "Diese Erinnerung endgültig löschen?",
+ "deleteConfirmBody": "Dadurch wird die Erinnerung entfernt statt archiviert. Dies kann nicht rückgängig gemacht werden."
},
"personaEvolutionTitle": "Persona-Entwicklung (experimentell)",
"personaEvolutionDescription": "Lassen Sie die Reflexion aktualisierte Selbstmodelle als Entwürfe zur Freigabe vorschlagen. Unabhängig vom Speicher; standardmäßig deaktiviert.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Gewicht · Ähnlichkeit",
"weightRecency": "Gewicht · Aktualität",
"weightImportance": "Gewicht · Wichtigkeit",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Erweiterte Speichereinstellungen",
+ "advancedHint": "Passe Extraktion, Prompt-Budget und Abruf-Ranking an. Leere Felder übernehmen die Standardwerte.",
+ "inheritedHint": "Leer verwendet den geerbten/Standardwert.",
+ "topKHint": "Bereich 1-100. Standard: {default}.",
+ "rrfKHint": "Bereich 1-1000. Standard: {default}.",
+ "similarityThresholdHint": "Bereich 0-1. Standard: {default}.",
+ "weightHint": "Nicht-negative Ranking-Gewichtung. Standard: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/es-ES/settings.json b/src/renderer/src/i18n/es-ES/settings.json
index 1000d60b6..388f7b434 100644
--- a/src/renderer/src/i18n/es-ES/settings.json
+++ b/src/renderer/src/i18n/es-ES/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Ya existe una memoria similar; no se añadió nada.",
"addDisabledHint": "La memoria está desactivada para este asistente. Actívala en Configuración antes de añadir memorias.",
"addSkipped": "No se añadió ninguna memoria.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "La búsqueda falló. Inténtalo de nuevo.",
+ "deletePermanent": "Eliminar permanentemente",
+ "deleteConfirmTitle": "¿Eliminar esta memoria permanentemente?",
+ "deleteConfirmBody": "Esto elimina la memoria en lugar de archivarla. Esta acción no se puede deshacer."
},
"personaEvolutionTitle": "Evolución de la persona (experimental)",
"personaEvolutionDescription": "Permite que la reflexión proponga modelos de sí mismo actualizados como borradores para tu aprobación. Independiente de la memoria; desactivado por defecto.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Peso · similitud",
"weightRecency": "Peso · actualidad",
"weightImportance": "Peso · importancia",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Configuración avanzada de memoria",
+ "advancedHint": "Ajusta la extracción, el presupuesto de prompt y la clasificación de recuperación. Deja los campos vacíos para heredar los valores predeterminados.",
+ "inheritedHint": "Vacío usa el valor heredado/predeterminado.",
+ "topKHint": "Rango 1-100. Predeterminado: {default}.",
+ "rrfKHint": "Rango 1-1000. Predeterminado: {default}.",
+ "similarityThresholdHint": "Rango 0-1. Predeterminado: {default}.",
+ "weightHint": "Peso de clasificación no negativo. Predeterminado: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/fa-IR/settings.json b/src/renderer/src/i18n/fa-IR/settings.json
index 1382fd18f..0e9ef1837 100644
--- a/src/renderer/src/i18n/fa-IR/settings.json
+++ b/src/renderer/src/i18n/fa-IR/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "حافظهٔ مشابهی از قبل وجود دارد؛ چیزی افزوده نشد.",
"addDisabledHint": "حافظه برای این دستیار غیرفعال است. پیش از افزودن حافظه، آن را در «پیکربندی» فعال کنید.",
"addSkipped": "هیچ حافظهای افزوده نشد.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "جستجو ناموفق بود. دوباره تلاش کنید.",
+ "deletePermanent": "حذف دائمی",
+ "deleteConfirmTitle": "این حافظه برای همیشه حذف شود؟",
+ "deleteConfirmBody": "این کار حافظه را بهجای بایگانی، حذف میکند. قابل بازگشت نیست."
},
"personaEvolutionTitle": "تکامل پرسونا (آزمایشی)",
"personaEvolutionDescription": "اجازه دهید بازتاب، مدلهای خودِ بهروزشده را بهصورت پیشنویس برای تأیید شما پیشنهاد دهد. مستقل از حافظه؛ بهطور پیشفرض خاموش.",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "وزن · شباهت",
"weightRecency": "وزن · تازگی",
"weightImportance": "وزن · اهمیت",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "تنظیمات پیشرفته حافظه",
+ "advancedHint": "استخراج، بودجه پرامپت و رتبهبندی بازیابی را تنظیم کنید. برای بهارثبردن پیشفرضها، فیلدها را خالی بگذارید.",
+ "inheritedHint": "خالی یعنی استفاده از مقدار ارثبریشده/پیشفرض.",
+ "topKHint": "بازه 1-100. پیشفرض: {default}.",
+ "rrfKHint": "بازه 1-1000. پیشفرض: {default}.",
+ "similarityThresholdHint": "بازه 0-1. پیشفرض: {default}.",
+ "weightHint": "وزن رتبهبندی نامنفی. پیشفرض: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/fr-FR/settings.json b/src/renderer/src/i18n/fr-FR/settings.json
index 99ca11df9..3757137ee 100644
--- a/src/renderer/src/i18n/fr-FR/settings.json
+++ b/src/renderer/src/i18n/fr-FR/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "Une mémoire similaire existe déjà ; rien n'a été ajouté.",
"addDisabledHint": "La mémoire est désactivée pour cet assistant. Activez-la dans la configuration avant d'ajouter des mémoires.",
"addSkipped": "Aucune mémoire n'a été ajoutée.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "La recherche a échoué. Réessayez.",
+ "deletePermanent": "Supprimer définitivement",
+ "deleteConfirmTitle": "Supprimer définitivement cette mémoire ?",
+ "deleteConfirmBody": "Cela supprime la mémoire au lieu de l’archiver. Cette action est irréversible."
},
"personaEvolutionTitle": "Évolution de la persona (expérimental)",
"personaEvolutionDescription": "Laissez la réflexion proposer des modèles de soi mis à jour sous forme de brouillons à approuver. Indépendant de la mémoire ; désactivé par défaut.",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "Poids · similarité",
"weightRecency": "Poids · récence",
"weightImportance": "Poids · importance",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Paramètres avancés de la mémoire",
+ "advancedHint": "Ajustez l’extraction, le budget de prompt et le classement de récupération. Laissez les champs vides pour hériter des valeurs par défaut.",
+ "inheritedHint": "Vide utilise la valeur héritée/par défaut.",
+ "topKHint": "Plage 1-100. Par défaut : {default}.",
+ "rrfKHint": "Plage 1-1000. Par défaut : {default}.",
+ "similarityThresholdHint": "Plage 0-1. Par défaut : {default}.",
+ "weightHint": "Poids de classement non négatif. Par défaut : {default}."
}
}
}
diff --git a/src/renderer/src/i18n/he-IL/settings.json b/src/renderer/src/i18n/he-IL/settings.json
index bb0f60a39..9477167b8 100644
--- a/src/renderer/src/i18n/he-IL/settings.json
+++ b/src/renderer/src/i18n/he-IL/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "כבר קיים זיכרון דומה; לא נוסף דבר.",
"addDisabledHint": "הזיכרון כבוי עבור עוזר זה. הפעל אותו בהגדרות לפני הוספת זיכרונות.",
"addSkipped": "לא נוסף זיכרון.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "החיפוש נכשל. נסה שוב.",
+ "deletePermanent": "מחיקה לצמיתות",
+ "deleteConfirmTitle": "למחוק את הזיכרון הזה לצמיתות?",
+ "deleteConfirmBody": "פעולה זו מסירה את הזיכרון במקום לארכב אותו. לא ניתן לבטל אותה."
},
"personaEvolutionTitle": "התפתחות פרסונה (ניסיוני)",
"personaEvolutionDescription": "אפשר לרפלקציה להציע מודלים עצמיים מעודכנים כטיוטות לאישורך. בלתי תלוי בזיכרון; כבוי כברירת מחדל.",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "משקל · דמיון",
"weightRecency": "משקל · עדכניות",
"weightImportance": "משקל · חשיבות",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "הגדרות זיכרון מתקדמות",
+ "advancedHint": "כוונן חילוץ, תקציב פרומפט ודירוג אחזור. השאר שדות ריקים כדי לרשת ברירות מחדל.",
+ "inheritedHint": "שדה ריק משתמש בערך בירושה/ברירת מחדל.",
+ "topKHint": "טווח 1-100. ברירת מחדל: {default}.",
+ "rrfKHint": "טווח 1-1000. ברירת מחדל: {default}.",
+ "similarityThresholdHint": "טווח 0-1. ברירת מחדל: {default}.",
+ "weightHint": "משקל דירוג לא שלילי. ברירת מחדל: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/id-ID/settings.json b/src/renderer/src/i18n/id-ID/settings.json
index bd024376d..72e6d0cd6 100644
--- a/src/renderer/src/i18n/id-ID/settings.json
+++ b/src/renderer/src/i18n/id-ID/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Memori serupa sudah ada; tidak ada yang ditambahkan.",
"addDisabledHint": "Memori dinonaktifkan untuk asisten ini. Aktifkan di Konfigurasi sebelum menambahkan memori.",
"addSkipped": "Tidak ada memori yang ditambahkan.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Pencarian gagal. Coba lagi.",
+ "deletePermanent": "Hapus permanen",
+ "deleteConfirmTitle": "Hapus memori ini secara permanen?",
+ "deleteConfirmBody": "Ini menghapus memori alih-alih mengarsipkannya. Tindakan ini tidak dapat dibatalkan."
},
"personaEvolutionTitle": "Evolusi persona (eksperimental)",
"personaEvolutionDescription": "Biarkan refleksi mengusulkan model diri yang diperbarui sebagai draf untuk persetujuan Anda. Independen dari memori; nonaktif secara default.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Bobot · kemiripan",
"weightRecency": "Bobot · kebaruan",
"weightImportance": "Bobot · kepentingan",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Pengaturan memori lanjutan",
+ "advancedHint": "Sesuaikan ekstraksi, anggaran prompt, dan peringkat pengambilan. Kosongkan kolom untuk mewarisi nilai default.",
+ "inheritedHint": "Kosong berarti menggunakan nilai turunan/default.",
+ "topKHint": "Rentang 1-100. Default: {default}.",
+ "rrfKHint": "Rentang 1-1000. Default: {default}.",
+ "similarityThresholdHint": "Rentang 0-1. Default: {default}.",
+ "weightHint": "Bobot peringkat non-negatif. Default: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/it-IT/settings.json b/src/renderer/src/i18n/it-IT/settings.json
index c10f988db..595ec0ff0 100644
--- a/src/renderer/src/i18n/it-IT/settings.json
+++ b/src/renderer/src/i18n/it-IT/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Esiste già una memoria simile; non è stato aggiunto nulla.",
"addDisabledHint": "La memoria è disattivata per questo assistente. Attivala nella configurazione prima di aggiungere memorie.",
"addSkipped": "Nessuna memoria è stata aggiunta.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Ricerca non riuscita. Riprova.",
+ "deletePermanent": "Elimina definitivamente",
+ "deleteConfirmTitle": "Eliminare definitivamente questa memoria?",
+ "deleteConfirmBody": "Questo rimuove la memoria invece di archiviarla. L’azione non può essere annullata."
},
"personaEvolutionTitle": "Evoluzione della persona (sperimentale)",
"personaEvolutionDescription": "Lascia che la riflessione proponga modelli di sé aggiornati come bozze da approvare. Indipendente dalla memoria; disattivata per impostazione predefinita.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Peso · similarità",
"weightRecency": "Peso · recency",
"weightImportance": "Peso · importanza",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Impostazioni avanzate della memoria",
+ "advancedHint": "Regola estrazione, budget del prompt e ranking del recupero. Lascia i campi vuoti per ereditare i valori predefiniti.",
+ "inheritedHint": "Vuoto usa il valore ereditato/predefinito.",
+ "topKHint": "Intervallo 1-100. Predefinito: {default}.",
+ "rrfKHint": "Intervallo 1-1000. Predefinito: {default}.",
+ "similarityThresholdHint": "Intervallo 0-1. Predefinito: {default}.",
+ "weightHint": "Peso di ranking non negativo. Predefinito: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/ja-JP/settings.json b/src/renderer/src/i18n/ja-JP/settings.json
index 6f6d78b29..1e06b7ab6 100644
--- a/src/renderer/src/i18n/ja-JP/settings.json
+++ b/src/renderer/src/i18n/ja-JP/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "類似のメモリが既に存在するため、追加されませんでした。",
"addDisabledHint": "このアシスタントのメモリは無効です。メモリを追加する前に「設定」で有効にしてください。",
"addSkipped": "メモリは追加されませんでした。",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "検索に失敗しました。もう一度お試しください。",
+ "deletePermanent": "完全に削除",
+ "deleteConfirmTitle": "このメモリを完全に削除しますか?",
+ "deleteConfirmBody": "メモリをアーカイブせずに削除します。この操作は元に戻せません。"
},
"personaEvolutionTitle": "人格進化(実験的)",
"personaEvolutionDescription": "リフレクションが更新後の自己モデルを下書きとして提案し、あなたの承認を待ちます。メモリとは独立で、既定では無効です。",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "重み · 類似度",
"weightRecency": "重み · 新しさ",
"weightImportance": "重み · 重要度",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "高度なメモリ設定",
+ "advancedHint": "抽出、プロンプト予算、検索ランキングを調整します。空欄にするとデフォルトを継承します。",
+ "inheritedHint": "空欄の場合は継承値/デフォルト値を使用します。",
+ "topKHint": "範囲 1-100。デフォルト: {default}。",
+ "rrfKHint": "範囲 1-1000。デフォルト: {default}。",
+ "similarityThresholdHint": "範囲 0-1。デフォルト: {default}。",
+ "weightHint": "負でないランキング重み。デフォルト: {default}。"
}
}
}
diff --git a/src/renderer/src/i18n/ko-KR/settings.json b/src/renderer/src/i18n/ko-KR/settings.json
index c4ab4cb55..a458aed0f 100644
--- a/src/renderer/src/i18n/ko-KR/settings.json
+++ b/src/renderer/src/i18n/ko-KR/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "유사한 메모리가 이미 있어 추가하지 않았습니다.",
"addDisabledHint": "이 어시스턴트의 메모리가 꺼져 있습니다. 메모리를 추가하려면 먼저 구성에서 활성화하세요.",
"addSkipped": "메모리가 추가되지 않았습니다.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "검색에 실패했습니다. 다시 시도하세요.",
+ "deletePermanent": "영구 삭제",
+ "deleteConfirmTitle": "이 메모리를 영구 삭제할까요?",
+ "deleteConfirmBody": "메모리를 보관하지 않고 제거합니다. 이 작업은 되돌릴 수 없습니다."
},
"personaEvolutionTitle": "페르소나 진화 (실험적)",
"personaEvolutionDescription": "리플렉션이 업데이트된 자기 모델을 초안으로 제안하여 승인을 받도록 합니다. 메모리와 독립적이며 기본값은 꺼짐입니다.",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "가중치 · 유사도",
"weightRecency": "가중치 · 최신성",
"weightImportance": "가중치 · 중요도",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "고급 메모리 설정",
+ "advancedHint": "추출, 프롬프트 예산, 검색 순위를 조정합니다. 비워 두면 기본값을 상속합니다.",
+ "inheritedHint": "비워 두면 상속/기본값을 사용합니다.",
+ "topKHint": "범위 1-100. 기본값: {default}.",
+ "rrfKHint": "범위 1-1000. 기본값: {default}.",
+ "similarityThresholdHint": "범위 0-1. 기본값: {default}.",
+ "weightHint": "음수가 아닌 순위 가중치. 기본값: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/ms-MY/settings.json b/src/renderer/src/i18n/ms-MY/settings.json
index df42f1ffc..e833e0eb4 100644
--- a/src/renderer/src/i18n/ms-MY/settings.json
+++ b/src/renderer/src/i18n/ms-MY/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Ingatan serupa sudah wujud; tiada apa-apa ditambah.",
"addDisabledHint": "Ingatan dimatikan untuk pembantu ini. Aktifkannya dalam Konfigurasi sebelum menambah ingatan.",
"addSkipped": "Tiada ingatan ditambah.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Carian gagal. Cuba lagi.",
+ "deletePermanent": "Padam secara kekal",
+ "deleteConfirmTitle": "Padam memori ini secara kekal?",
+ "deleteConfirmBody": "Ini membuang memori dan bukannya mengarkibkannya. Tindakan ini tidak boleh dibuat asal."
},
"personaEvolutionTitle": "Evolusi persona (eksperimental)",
"personaEvolutionDescription": "Benarkan refleksi mencadangkan model diri yang dikemas kini sebagai draf untuk kelulusan anda. Bebas daripada memori; dimatikan secara lalai.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Berat · persamaan",
"weightRecency": "Berat · kebaharuan",
"weightImportance": "Berat · kepentingan",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Tetapan memori lanjutan",
+ "advancedHint": "Laraskan pengekstrakan, bajet prompt dan penarafan dapatan semula. Biarkan medan kosong untuk mewarisi lalai.",
+ "inheritedHint": "Kosong menggunakan nilai diwarisi/lalai.",
+ "topKHint": "Julat 1-100. Lalai: {default}.",
+ "rrfKHint": "Julat 1-1000. Lalai: {default}.",
+ "similarityThresholdHint": "Julat 0-1. Lalai: {default}.",
+ "weightHint": "Berat penarafan tidak negatif. Lalai: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/pl-PL/settings.json b/src/renderer/src/i18n/pl-PL/settings.json
index 8d103f135..418b9e4f4 100644
--- a/src/renderer/src/i18n/pl-PL/settings.json
+++ b/src/renderer/src/i18n/pl-PL/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Podobne wspomnienie już istnieje; nic nie dodano.",
"addDisabledHint": "Pamięć jest wyłączona dla tego asystenta. Włącz ją w Konfiguracji, zanim dodasz wspomnienia.",
"addSkipped": "Nie dodano żadnego wspomnienia.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Wyszukiwanie nie powiodło się. Spróbuj ponownie.",
+ "deletePermanent": "Usuń trwale",
+ "deleteConfirmTitle": "Trwale usunąć tę pamięć?",
+ "deleteConfirmBody": "Spowoduje to usunięcie pamięci zamiast jej zarchiwizowania. Tej operacji nie można cofnąć."
},
"personaEvolutionTitle": "Ewolucja persony (eksperymentalna)",
"personaEvolutionDescription": "Pozwól, aby refleksja proponowała zaktualizowane modele siebie jako wersje robocze do zatwierdzenia. Niezależna od pamięci; domyślnie wyłączona.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Waga · podobieństwo",
"weightRecency": "Waga · aktualność",
"weightImportance": "Waga · ważność",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Zaawansowane ustawienia pamięci",
+ "advancedHint": "Dostosuj ekstrakcję, budżet promptu i ranking wyszukiwania. Pozostaw pola puste, aby dziedziczyć wartości domyślne.",
+ "inheritedHint": "Puste używa wartości odziedziczonej/domyślnej.",
+ "topKHint": "Zakres 1-100. Domyślnie: {default}.",
+ "rrfKHint": "Zakres 1-1000. Domyślnie: {default}.",
+ "similarityThresholdHint": "Zakres 0-1. Domyślnie: {default}.",
+ "weightHint": "Nieujemna waga rankingu. Domyślnie: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/pt-BR/settings.json b/src/renderer/src/i18n/pt-BR/settings.json
index df1bb3c1e..e7390a770 100644
--- a/src/renderer/src/i18n/pt-BR/settings.json
+++ b/src/renderer/src/i18n/pt-BR/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "Já existe uma memória semelhante; nada foi adicionado.",
"addDisabledHint": "A memória está desativada para este assistente. Ative-a na Configuração antes de adicionar memórias.",
"addSkipped": "Nenhuma memória foi adicionada.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "A busca falhou. Tente novamente.",
+ "deletePermanent": "Excluir permanentemente",
+ "deleteConfirmTitle": "Excluir esta memória permanentemente?",
+ "deleteConfirmBody": "Isso remove a memória em vez de arquivá-la. Esta ação não pode ser desfeita."
},
"personaEvolutionTitle": "Evolução da persona (experimental)",
"personaEvolutionDescription": "Permita que a reflexão proponha modelos de si atualizados como rascunhos para sua aprovação. Independente da memória; desativado por padrão.",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "Peso · similaridade",
"weightRecency": "Peso · recência",
"weightImportance": "Peso · importância",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Configurações avançadas de memória",
+ "advancedHint": "Ajuste extração, orçamento de prompt e ranqueamento de recuperação. Deixe os campos vazios para herdar os padrões.",
+ "inheritedHint": "Vazio usa o valor herdado/padrão.",
+ "topKHint": "Intervalo 1-100. Padrão: {default}.",
+ "rrfKHint": "Intervalo 1-1000. Padrão: {default}.",
+ "similarityThresholdHint": "Intervalo 0-1. Padrão: {default}.",
+ "weightHint": "Peso de ranqueamento não negativo. Padrão: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/ru-RU/settings.json b/src/renderer/src/i18n/ru-RU/settings.json
index 1cbe8b15a..c2a2bde61 100644
--- a/src/renderer/src/i18n/ru-RU/settings.json
+++ b/src/renderer/src/i18n/ru-RU/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "Похожее воспоминание уже есть; ничего не добавлено.",
"addDisabledHint": "Память отключена для этого ассистента. Включите её в настройках, прежде чем добавлять воспоминания.",
"addSkipped": "Воспоминание не добавлено.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Поиск не удался. Попробуйте ещё раз.",
+ "deletePermanent": "Удалить навсегда",
+ "deleteConfirmTitle": "Удалить эту память навсегда?",
+ "deleteConfirmBody": "Память будет удалена, а не архивирована. Это действие нельзя отменить."
},
"personaEvolutionTitle": "Эволюция персоны (эксперимент)",
"personaEvolutionDescription": "Позвольте рефлексии предлагать обновлённые модели себя в виде черновиков для вашего одобрения. Независимо от памяти; по умолчанию выключено.",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "Вес · сходство",
"weightRecency": "Вес · давность",
"weightImportance": "Вес · важность",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Расширенные настройки памяти",
+ "advancedHint": "Настройте извлечение, бюджет промпта и ранжирование поиска. Оставьте поля пустыми, чтобы наследовать значения по умолчанию.",
+ "inheritedHint": "Пустое поле использует унаследованное/значение по умолчанию.",
+ "topKHint": "Диапазон 1-100. По умолчанию: {default}.",
+ "rrfKHint": "Диапазон 1-1000. По умолчанию: {default}.",
+ "similarityThresholdHint": "Диапазон 0-1. По умолчанию: {default}.",
+ "weightHint": "Неотрицательный вес ранжирования. По умолчанию: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/tr-TR/settings.json b/src/renderer/src/i18n/tr-TR/settings.json
index 3b97ab37d..379e07452 100644
--- a/src/renderer/src/i18n/tr-TR/settings.json
+++ b/src/renderer/src/i18n/tr-TR/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Benzer bir anı zaten var; hiçbir şey eklenmedi.",
"addDisabledHint": "Bu asistan için bellek kapalı. Anı eklemeden önce Yapılandırma'dan etkinleştirin.",
"addSkipped": "Hiçbir anı eklenmedi.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Arama başarısız oldu. Tekrar deneyin.",
+ "deletePermanent": "Kalıcı olarak sil",
+ "deleteConfirmTitle": "Bu belleği kalıcı olarak sil?",
+ "deleteConfirmBody": "Bu işlem belleği arşivlemek yerine kaldırır. Geri alınamaz."
},
"personaEvolutionTitle": "Kişilik evrimi (deneysel)",
"personaEvolutionDescription": "Yansımanın güncellenmiş benlik modellerini onayınız için taslak olarak önermesine izin verin. Bellekten bağımsızdır; varsayılan olarak kapalıdır.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Ağırlık · benzerlik",
"weightRecency": "Ağırlık · güncellik",
"weightImportance": "Ağırlık · önem",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Gelişmiş bellek ayarları",
+ "advancedHint": "Çıkarımı, istem bütçesini ve geri çağırma sıralamasını ayarlayın. Varsayılanları devralmak için alanları boş bırakın.",
+ "inheritedHint": "Boş bırakılırsa devralınan/varsayılan değer kullanılır.",
+ "topKHint": "Aralık 1-100. Varsayılan: {default}.",
+ "rrfKHint": "Aralık 1-1000. Varsayılan: {default}.",
+ "similarityThresholdHint": "Aralık 0-1. Varsayılan: {default}.",
+ "weightHint": "Negatif olmayan sıralama ağırlığı. Varsayılan: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/vi-VN/settings.json b/src/renderer/src/i18n/vi-VN/settings.json
index c1afb03d5..e960ddd33 100644
--- a/src/renderer/src/i18n/vi-VN/settings.json
+++ b/src/renderer/src/i18n/vi-VN/settings.json
@@ -222,10 +222,10 @@
"addDuplicate": "Đã có một ký ức tương tự; không có gì được thêm.",
"addDisabledHint": "Bộ nhớ đã tắt cho trợ lý này. Hãy bật trong phần Cấu hình trước khi thêm ký ức.",
"addSkipped": "Không có ký ức nào được thêm.",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "Tìm kiếm thất bại. Hãy thử lại.",
+ "deletePermanent": "Xóa vĩnh viễn",
+ "deleteConfirmTitle": "Xóa vĩnh viễn ký ức này?",
+ "deleteConfirmBody": "Thao tác này xóa ký ức thay vì lưu trữ. Không thể hoàn tác."
},
"personaEvolutionTitle": "Tiến hóa tính cách (thử nghiệm)",
"personaEvolutionDescription": "Cho phép quá trình phản tư đề xuất các mô hình bản thân cập nhật dưới dạng bản nháp để bạn phê duyệt. Độc lập với bộ nhớ; mặc định tắt.",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "Trọng số · tương đồng",
"weightRecency": "Trọng số · gần đây",
"weightImportance": "Trọng số · quan trọng",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "Cài đặt bộ nhớ nâng cao",
+ "advancedHint": "Tinh chỉnh trích xuất, ngân sách prompt và xếp hạng truy xuất. Để trống để kế thừa mặc định.",
+ "inheritedHint": "Để trống sẽ dùng giá trị kế thừa/mặc định.",
+ "topKHint": "Phạm vi 1-100. Mặc định: {default}.",
+ "rrfKHint": "Phạm vi 1-1000. Mặc định: {default}.",
+ "similarityThresholdHint": "Phạm vi 0-1. Mặc định: {default}.",
+ "weightHint": "Trọng số xếp hạng không âm. Mặc định: {default}."
}
}
}
diff --git a/src/renderer/src/i18n/zh-CN/settings.json b/src/renderer/src/i18n/zh-CN/settings.json
index 5695ba6dd..2cabb3337 100644
--- a/src/renderer/src/i18n/zh-CN/settings.json
+++ b/src/renderer/src/i18n/zh-CN/settings.json
@@ -219,10 +219,10 @@
"addDuplicate": "已记住类似内容,未重复添加。",
"addDisabledHint": "该助手的记忆已关闭,请先在「配置」中启用后再添加记忆。",
"addSkipped": "未添加记忆。",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "搜索失败,请重试。",
+ "deletePermanent": "永久删除",
+ "deleteConfirmTitle": "永久删除这条记忆?",
+ "deleteConfirmBody": "这会直接移除该记忆,而不是归档。此操作无法撤销。"
},
"compactionThreshold": "触发阈值",
"compactionRetainPairs": "保留最近消息对",
@@ -2418,13 +2418,13 @@
"weightSimilarity": "权重 · 相似度",
"weightRecency": "权重 · 时近度",
"weightImportance": "权重 · 重要度",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "高级记忆设置",
+ "advancedHint": "调整抽取、提示词预算和检索排序。字段留空则继承默认值。",
+ "inheritedHint": "留空使用继承值/默认值。",
+ "topKHint": "范围 1-100。默认:{default}。",
+ "rrfKHint": "范围 1-1000。默认:{default}。",
+ "similarityThresholdHint": "范围 0-1。默认:{default}。",
+ "weightHint": "非负排序权重。默认:{default}。"
}
}
}
diff --git a/src/renderer/src/i18n/zh-HK/settings.json b/src/renderer/src/i18n/zh-HK/settings.json
index ad1d88a87..6b9b18931 100644
--- a/src/renderer/src/i18n/zh-HK/settings.json
+++ b/src/renderer/src/i18n/zh-HK/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "已記住類似內容,未重複新增。",
"addDisabledHint": "此助手的記憶已關閉,請先在「配置」中啟用後再新增記憶。",
"addSkipped": "未新增記憶。",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "搜尋失敗,請再試一次。",
+ "deletePermanent": "永久刪除",
+ "deleteConfirmTitle": "永久刪除這段記憶?",
+ "deleteConfirmBody": "這會直接移除該記憶,而不是封存。此操作無法復原。"
},
"personaEvolutionTitle": "人格演化(實驗)",
"personaEvolutionDescription": "讓反思以草稿形式提出更新後的自我模型,交由你審核。獨立於記憶開關,預設關閉。",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "權重 · 相似度",
"weightRecency": "權重 · 時近度",
"weightImportance": "權重 · 重要度",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "進階記憶設定",
+ "advancedHint": "調整擷取、提示詞預算和檢索排序。欄位留空則繼承預設值。",
+ "inheritedHint": "留空會使用繼承值/預設值。",
+ "topKHint": "範圍 1-100。預設:{default}。",
+ "rrfKHint": "範圍 1-1000。預設:{default}。",
+ "similarityThresholdHint": "範圍 0-1。預設:{default}。",
+ "weightHint": "非負排序權重。預設:{default}。"
}
}
}
diff --git a/src/renderer/src/i18n/zh-TW/settings.json b/src/renderer/src/i18n/zh-TW/settings.json
index 76815dd31..7160fe60c 100644
--- a/src/renderer/src/i18n/zh-TW/settings.json
+++ b/src/renderer/src/i18n/zh-TW/settings.json
@@ -2291,10 +2291,10 @@
"addDuplicate": "已記住類似內容,未重複新增。",
"addDisabledHint": "此助手的記憶已關閉,請先在「配置」中啟用後再新增記憶。",
"addSkipped": "未新增記憶。",
- "searchFailed": "Search failed. Try again.",
- "deletePermanent": "Delete permanently",
- "deleteConfirmTitle": "Delete this memory permanently?",
- "deleteConfirmBody": "This removes the memory instead of archiving it. This cannot be undone."
+ "searchFailed": "搜尋失敗,請再試一次。",
+ "deletePermanent": "永久刪除",
+ "deleteConfirmTitle": "永久刪除這段記憶?",
+ "deleteConfirmBody": "這會直接移除該記憶,而不是封存。此操作無法復原。"
},
"personaEvolutionTitle": "人格演化(實驗)",
"personaEvolutionDescription": "讓反思以草稿形式提出更新後的自我模型,交由你審核。獨立於記憶開關,預設關閉。",
@@ -2427,13 +2427,13 @@
"weightSimilarity": "權重 · 相似度",
"weightRecency": "權重 · 時近度",
"weightImportance": "權重 · 重要度",
- "advancedTitle": "Advanced memory settings",
- "advancedHint": "Tune extraction, prompt budget, and retrieval ranking. Leave fields empty to inherit defaults.",
- "inheritedHint": "Empty uses inherited/default value.",
- "topKHint": "Range 1-100. Default: {default}.",
- "rrfKHint": "Range 1-1000. Default: {default}.",
- "similarityThresholdHint": "Range 0-1. Default: {default}.",
- "weightHint": "Non-negative ranking weight. Default: {default}."
+ "advancedTitle": "進階記憶設定",
+ "advancedHint": "調整擷取、提示詞預算與檢索排序。欄位留空則繼承預設值。",
+ "inheritedHint": "留空會使用繼承值/預設值。",
+ "topKHint": "範圍 1-100。預設:{default}。",
+ "rrfKHint": "範圍 1-1000。預設:{default}。",
+ "similarityThresholdHint": "範圍 0-1。預設:{default}。",
+ "weightHint": "非負排序權重。預設:{default}。"
}
}
}
diff --git a/test/main/routes/memoryDto.test.ts b/test/main/routes/memoryDto.test.ts
index ba33e61ab..fec279793 100644
--- a/test/main/routes/memoryDto.test.ts
+++ b/test/main/routes/memoryDto.test.ts
@@ -197,11 +197,12 @@ describe('formatMemorySourceRecordContent', () => {
{ type: 'content', content: 'answer body' },
{ type: 'reasoning', text: 'reasoning note' },
{ type: 'reasoning_content', content: 'reasoning block' },
+ { reasoning_content: 'legacy reasoning field' },
{ type: 'tool_call', content: '{"raw":true}' }
])
)
)
- ).toBe('answer body reasoning note reasoning block')
+ ).toBe('answer body')
})
it('returns empty text for malformed or unsupported records', () => {
From 71e4c0f3aed4c0fbdda3d2c62cd6b5504a5e1d2e Mon Sep 17 00:00:00 2001
From: yyhhyyyyyy
Date: Mon, 22 Jun 2026 11:36:35 +0800
Subject: [PATCH 3/3] fix(memory): gate restore button when memory off
---
src/renderer/settings/components/MemoryManagerPanel.vue | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/renderer/settings/components/MemoryManagerPanel.vue b/src/renderer/settings/components/MemoryManagerPanel.vue
index 7ce713e4c..5b90fa333 100644
--- a/src/renderer/settings/components/MemoryManagerPanel.vue
+++ b/src/renderer/settings/components/MemoryManagerPanel.vue
@@ -256,6 +256,7 @@
v-if="memory.status === 'archived'"
variant="ghost"
size="sm"
+ :disabled="memoryDisabled"
class="h-7 px-2 text-xs"
:aria-label="t('settings.deepchatAgents.memoryManager.restore')"
@click="handleRestore(memory.id)"
@@ -1033,6 +1034,7 @@ async function handleSetAnchor(versionId: string, anchored: boolean): Promise {
+ if (memoryDisabled.value) return
try {
const ok = await memoryClient.restore(props.agentId, memoryId)
if (!ok) return notifyActionFailed()