Memory page and search inside chats, files and memory (Ctrl+K, GET /search)
ci / ui (push) Waiting to run
ci / check (push) Failing after 7s
ci / start (push) Failing after 5s

The account's memory as the web's Settings -> Memory keeps it: add, change,
remove, over /api/v2/memories. Ctrl+K asks the server's search once two
letters are typed and lists the hits below the titles, the matched ranges
drawn as <mark> elements over plain text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-10 13:44:49 +00:00
1 parent be70e1a628
commit d691a4dcfa
9 files changed
+515 -11

No files matched your search

+7
View File
@@ -24,6 +24,13 @@ All notable changes to LLeMbas Desktop. The format follows
folders, nested and folded as the server keeps them, then the rest by day. A chat's menu pins folders, nested and folded as the server keeps them, then the rest by day. A chat's menu pins
it and moves it into a folder or a new one; a folder's menu renames it, makes one inside it, or it and moves it into a folder or a new one; a folder's menu renames it, makes one inside it, or
deletes it (its chats stay, back in the list). The search line still lists every match. deletes it (its chats stay, back in the list). The search line still lists every match.
- **Memory.** A page in the sidebar (and in Ctrl+K) lists what the account's models remember about
the person, as the web's Settings → Memory does: add a record (into a data group, where there is
more than one), change it, remove it. A record a model noted says so.
- **Search inside.** Ctrl+K finds more than titles: once two letters are typed it asks the
server's search, and lists below the titles what it found inside chats, in the file library and
in memory, each with the words around the match, the match marked. A chat opens, a file opens
in the viewer, a memory opens the Memory page at it. A server without search finds titles only.
### Fixed ### Fixed
+65
View File
@@ -484,6 +484,15 @@ KEYS = (
"New folder…", "New folder…",
"Create", "Create",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.",
# palette.js, memory.js: search inside, and the memory page
"Memory",
"Add",
"Data group",
"Remove this memory?",
"Noted by a model",
"What your models remember about you, across chats. Change or remove anything here.",
"Something to remember…",
"Nothing remembered yet.",
) )
# The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the # The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the
@@ -732,6 +741,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Nová složka…", "New folder…": "Nová složka…",
"Create": "Vytvořit", "Create": "Vytvořit",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s“ a složky v ní se smažou. Jejich konverzace zůstanou, zpět v seznamu.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s“ a složky v ní se smažou. Jejich konverzace zůstanou, zpět v seznamu.",
"Add": "Přidat",
"Data group": "Skupina dat",
"Remove this memory?": "Odstranit tento záznam z paměti?",
"Noted by a model": "Zaznamenal model",
"What your models remember about you, across chats. Change or remove anything here.": "Co si o vás vaše modely pamatují napříč konverzacemi. Cokoli tu můžete změnit nebo odstranit.",
"Something to remember…": "Něco k zapamatování…",
"Nothing remembered yet.": "Zatím si nic nepamatuje.",
}, },
"de": { "de": {
"Sign in again: the server no longer accepts this app's sign-in.": "Melden Sie sich erneut an: Der Server nimmt die Anmeldung dieser App nicht mehr an.", "Sign in again: the server no longer accepts this app's sign-in.": "Melden Sie sich erneut an: Der Server nimmt die Anmeldung dieser App nicht mehr an.",
@@ -975,6 +991,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Neuer Ordner…", "New folder…": "Neuer Ordner…",
"Create": "Erstellen", "Create": "Erstellen",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s“ und die Ordner darin werden gelöscht. Ihre Chats bleiben, wieder in der Liste.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s“ und die Ordner darin werden gelöscht. Ihre Chats bleiben, wieder in der Liste.",
"Add": "Hinzufügen",
"Data group": "Datengruppe",
"Remove this memory?": "Diese Erinnerung entfernen?",
"Noted by a model": "Von einem Modell notiert",
"What your models remember about you, across chats. Change or remove anything here.": "Was Ihre Modelle über Sie behalten, über alle Chats hinweg. Hier können Sie alles ändern oder entfernen.",
"Something to remember…": "Etwas, das behalten werden soll…",
"Nothing remembered yet.": "Noch nichts behalten.",
}, },
"es": { "es": {
"Sign in again: the server no longer accepts this app's sign-in.": "Vuelva a iniciar sesión: el servidor ya no acepta el inicio de sesión de esta aplicación.", "Sign in again: the server no longer accepts this app's sign-in.": "Vuelva a iniciar sesión: el servidor ya no acepta el inicio de sesión de esta aplicación.",
@@ -1218,6 +1241,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Nueva carpeta…", "New folder…": "Nueva carpeta…",
"Create": "Crear", "Create": "Crear",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "Se eliminan «%(name)s» y las carpetas que contiene. Sus chats se quedan, de vuelta en la lista.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "Se eliminan «%(name)s» y las carpetas que contiene. Sus chats se quedan, de vuelta en la lista.",
"Add": "Añadir",
"Data group": "Grupo de datos",
"Remove this memory?": "¿Quitar este recuerdo?",
"Noted by a model": "Anotado por un modelo",
"What your models remember about you, across chats. Change or remove anything here.": "Lo que sus modelos recuerdan de usted entre chats. Aquí puede cambiar o quitar cualquier cosa.",
"Something to remember…": "Algo que recordar…",
"Nothing remembered yet.": "Todavía no se recuerda nada.",
}, },
"fr": { "fr": {
"Sign in again: the server no longer accepts this app's sign-in.": "Reconnectez-vous : le serveur n’accepte plus la connexion de cette application.", "Sign in again: the server no longer accepts this app's sign-in.": "Reconnectez-vous : le serveur n’accepte plus la connexion de cette application.",
@@ -1461,6 +1491,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Nouveau dossier…", "New folder…": "Nouveau dossier…",
"Create": "Créer", "Create": "Créer",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "« %(name)s » et les dossiers qu’il contient sont supprimés. Leurs conversations restent, de retour dans la liste.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "« %(name)s » et les dossiers qu’il contient sont supprimés. Leurs conversations restent, de retour dans la liste.",
"Add": "Ajouter",
"Data group": "Groupe de données",
"Remove this memory?": "Supprimer ce souvenir ?",
"Noted by a model": "Noté par un modèle",
"What your models remember about you, across chats. Change or remove anything here.": "Ce que vos modèles retiennent de vous, d’une conversation à l’autre. Modifiez ou supprimez ce que vous voulez ici.",
"Something to remember…": "Quelque chose à retenir…",
"Nothing remembered yet.": "Rien n’est encore retenu.",
}, },
"hu": { "hu": {
"Sign in again: the server no longer accepts this app's sign-in.": "Jelentkezzen be újra: a szerver már nem fogadja el ennek az alkalmazásnak a bejelentkezését.", "Sign in again: the server no longer accepts this app's sign-in.": "Jelentkezzen be újra: a szerver már nem fogadja el ennek az alkalmazásnak a bejelentkezését.",
@@ -1704,6 +1741,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Új mappa…", "New folder…": "Új mappa…",
"Create": "Létrehozás", "Create": "Létrehozás",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "A(z) „%(name)s” és a benne lévő mappák törlődnek. A beszélgetéseik megmaradnak, vissza a listába.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "A(z) „%(name)s” és a benne lévő mappák törlődnek. A beszélgetéseik megmaradnak, vissza a listába.",
"Add": "Hozzáadás",
"Data group": "Adatcsoport",
"Remove this memory?": "Eltávolítja ezt az emléket?",
"Noted by a model": "Egy modell jegyezte fel",
"What your models remember about you, across chats. Change or remove anything here.": "Amit a modelljei megjegyeznek Önről a beszélgetéseken át. Itt bármit módosíthat vagy eltávolíthat.",
"Something to remember…": "Valami, amit meg kell jegyezni…",
"Nothing remembered yet.": "Még nincs megjegyezve semmi.",
}, },
"it": { "it": {
"Sign in again: the server no longer accepts this app's sign-in.": "Accedi di nuovo: il server non accetta più l’accesso di questa app.", "Sign in again: the server no longer accepts this app's sign-in.": "Accedi di nuovo: il server non accetta più l’accesso di questa app.",
@@ -1947,6 +1991,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Nuova cartella…", "New folder…": "Nuova cartella…",
"Create": "Crea", "Create": "Crea",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "«%(name)s» e le cartelle al suo interno vengono eliminate. Le loro chat restano, di nuovo nell’elenco.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "«%(name)s» e le cartelle al suo interno vengono eliminate. Le loro chat restano, di nuovo nell’elenco.",
"Add": "Aggiungi",
"Data group": "Gruppo di dati",
"Remove this memory?": "Rimuovere questo ricordo?",
"Noted by a model": "Annotato da un modello",
"What your models remember about you, across chats. Change or remove anything here.": "Ciò che i tuoi modelli ricordano di te tra una chat e l’altra. Qui puoi cambiare o rimuovere qualsiasi cosa.",
"Something to remember…": "Qualcosa da ricordare…",
"Nothing remembered yet.": "Ancora niente da ricordare.",
}, },
"pl": { "pl": {
"Sign in again: the server no longer accepts this app's sign-in.": "Zaloguj się ponownie: serwer nie przyjmuje już logowania tej aplikacji.", "Sign in again: the server no longer accepts this app's sign-in.": "Zaloguj się ponownie: serwer nie przyjmuje już logowania tej aplikacji.",
@@ -2190,6 +2241,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Nowy folder…", "New folder…": "Nowy folder…",
"Create": "Utwórz", "Create": "Utwórz",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s” i foldery w nim zostaną usunięte. Ich czaty zostaną, z powrotem na liście.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s” i foldery w nim zostaną usunięte. Ich czaty zostaną, z powrotem na liście.",
"Add": "Dodaj",
"Data group": "Grupa danych",
"Remove this memory?": "Usunąć to wspomnienie?",
"Noted by a model": "Zanotowane przez model",
"What your models remember about you, across chats. Change or remove anything here.": "Co Twoje modele pamiętają o Tobie we wszystkich czatach. Tutaj możesz wszystko zmienić lub usunąć.",
"Something to remember…": "Coś do zapamiętania…",
"Nothing remembered yet.": "Na razie nic nie zapamiętano.",
}, },
"sk": { "sk": {
"Sign in again: the server no longer accepts this app's sign-in.": "Prihláste sa znova: server už neprijíma prihlásenie tejto aplikácie.", "Sign in again: the server no longer accepts this app's sign-in.": "Prihláste sa znova: server už neprijíma prihlásenie tejto aplikácie.",
@@ -2433,6 +2491,13 @@ OWN: dict[str, dict[str, str]] = {
"New folder…": "Nový priečinok…", "New folder…": "Nový priečinok…",
"Create": "Vytvoriť", "Create": "Vytvoriť",
"“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s“ a priečinky v ňom sa zmažú. Ich konverzácie zostanú, späť v zozname.", "“%(name)s” and the folders inside it are deleted. Their chats stay, back in the list.": "„%(name)s“ a priečinky v ňom sa zmažú. Ich konverzácie zostanú, späť v zozname.",
"Add": "Pridať",
"Data group": "Skupina údajov",
"Remove this memory?": "Odstrániť tento záznam z pamäte?",
"Noted by a model": "Zaznamenal model",
"What your models remember about you, across chats. Change or remove anything here.": "Čo si o vás vaše modely pamätajú naprieč konverzáciami. Čokoľvek tu môžete zmeniť alebo odstrániť.",
"Something to remember…": "Niečo na zapamätanie…",
"Nothing remembered yet.": "Zatiaľ si nič nepamätá.",
}, },
} }
+93
View File
@@ -1200,6 +1200,99 @@ test("F8: at 390 px the folders stay inside the sidebar", async () => {
assert.deepEqual(p.errors, []) assert.deepEqual(p.errors, [])
}) })
// G8: search inside (GET /search, fixture search.json) and the memory page (memories.json).
function g8(over = {}) {
return {
status: status(),
routes: baseRoutes({
"GET /search": fixture("search"),
"GET /memories": fixture("memories"),
"POST /memories": { memory: { ...fixture("memory").memory, id: "mem2", content: "Walks to Bree on Sundays." } },
"PATCH /memories/mem1": { memory: { ...fixture("memory").memory, content: "Prefers lembas cold." } },
"DELETE /memories/mem1": null,
"GET /files/content": { name: "bread.md", path: "/home/frodo/bread.md", text: "# Lembas recipe", kind: "markdown" },
...over,
}),
streams: {},
}
}
test("G8: the palette finds words inside chats, files and memory through the server, the matches marked as text", async () => {
const p = await page(g8())
await p.keyboard.press("Control+k")
await p.fill(".palette__input", "lembas")
await p.waitForSelector(".palette__snippet")
const [q] = await p.evaluate(() => window.__mock.api("GET", "/search"))
assert.match(q.args.path, /^\/search\?q=lembas&in=all&limit=8$/)
const hits = await p.$$eval(".palette__item:has(.palette__snippet)", (l) => l.map((x) => ({ title: x.querySelector(".palette__title").textContent, mark: x.querySelector("mark")?.textContent, hint: x.querySelector(".palette__hint").textContent })))
assert.deepEqual(hits.map((x) => x.hint), ["Chats", "Files", "Memory"])
assert.equal(hits[0].title, "Waybread")
assert.equal(hits[0].mark, "lembas")
assert.equal(hits[2].mark, "lembas")
await shot(p, "g8-search-1280")
// A memory opens the memory page at it.
await p.click(".palette__item:has(.palette__hint:text('Memory'))")
await p.waitForSelector('#page-memory:not([hidden]) .mrow.is-marked[data-id="mem1"]')
assert.deepEqual(p.errors, [])
})
test("G8: a chat found inside opens; a server without search finds nothing more, and says nothing", async () => {
let p = await page(g8())
await p.keyboard.press("Control+k")
await p.fill(".palette__input", "lembas")
await p.waitForSelector(".palette__snippet")
await p.click(".palette__item:has(.palette__hint:text('Chats'))")
await p.waitForFunction(() => window.__mock.api("GET", "/chats/chat1").length > 0)
await p.context().close()
p = await page(g8({ "GET /search": { __error: { status: 404, detail: "Not Found" } } }))
await p.keyboard.press("Control+k")
await p.fill(".palette__input", "lembas")
await p.waitForFunction(() => window.__mock.api("GET", "/search").length === 1)
await p.waitForTimeout(100)
assert.equal(await p.locator(".palette__snippet").count(), 0)
assert.deepEqual(p.errors, [])
})
test("G8: the memory page adds, changes and removes what the models remember", async () => {
const p = await page(g8())
await p.click("#nav-memory")
await p.waitForSelector('.mrow[data-id="mem1"]')
assert.match(await p.textContent('.mrow[data-id="mem1"]'), /Prefers lembas warm\./)
await p.fill("#memory-new", "Walks to Bree on Sundays.")
await p.click(".mnew button[type=submit]")
await p.waitForSelector('.mrow[data-id="mem2"]')
assert.deepEqual((await p.evaluate(() => window.__mock.api("POST", "/memories")))[0].args.body, { content: "Walks to Bree on Sundays." })
assert.equal(await p.inputValue("#memory-new"), "")
await p.click('.mrow[data-id="mem1"] .btn >> text=Edit')
await p.fill(".mrow.is-editing textarea", "Prefers lembas cold.")
await p.click(".mrow.is-editing .btn--primary")
await p.waitForFunction(() => /cold/.test(document.querySelector('.mrow[data-id="mem1"]').textContent))
assert.deepEqual((await p.evaluate(() => window.__mock.api("PATCH", "/memories/mem1")))[0].args.body, { content: "Prefers lembas cold." })
await p.click('.mrow[data-id="mem1"] .tool')
await p.click(".menu .menu__item >> text=Remove")
await p.click("dialog[open] .btn--danger")
await p.waitForFunction(() => !document.querySelector('.mrow[data-id="mem1"]'))
assert.deepEqual(p.errors, [])
})
test("G8: at 390 px the memory page and the palette's hits stay inside the window", async () => {
const p = await page(g8(), { width: 390, height: 844 })
await p.evaluate(() => document.getElementById("nav-memory").click())
await p.waitForSelector('.mrow[data-id="mem1"]')
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
await shot(p, "g8-memory-390")
await p.setViewportSize({ width: 1280, height: 860 })
await shot(p, "g8-memory-1280")
await p.setViewportSize({ width: 390, height: 844 })
await p.keyboard.press("Control+k")
await p.fill(".palette__input", "lembas")
await p.waitForSelector(".palette__snippet")
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
await shot(p, "g8-search-390")
assert.deepEqual(p.errors, [])
})
// --- Run ---------------------------------------------------------------------------------------- // --- Run ----------------------------------------------------------------------------------------
const only = process.argv[2] || "" const only = process.argv[2] || ""
+27
View File
@@ -1205,6 +1205,11 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
.palette__icon { display: grid; place-items: center; color: var(--ink-muted); } .palette__icon { display: grid; place-items: center; color: var(--ink-muted); }
.palette__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .palette__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.palette__hint { font-size: var(--text-xs); color: var(--ink-faint); white-space: nowrap; } .palette__hint { font-size: var(--text-xs); color: var(--ink-faint); white-space: nowrap; }
/* A hit found inside: its title over the snippet, the words that matched marked. */
.palette__label--two { display: grid; padding: var(--sp-1) 0; white-space: normal; }
.palette__title, .palette__snippet { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.palette__snippet { font-size: var(--text-xs); color: var(--ink-muted); }
.palette__snippet mark { color: var(--ink); background: var(--surface-active); border-radius: var(--radius-sm); }
.palette__none { padding: var(--sp-3); font-size: var(--text-sm); color: var(--ink-muted); } .palette__none { padding: var(--sp-3); font-size: var(--text-sm); color: var(--ink-muted); }
@@ -1691,6 +1696,28 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
.arch__page { display: grid; gap: var(--sp-3); } .arch__page { display: grid; gap: var(--sp-3); }
.head__unarchive { white-space: nowrap; } .head__unarchive { white-space: nowrap; }
/* --- Memory: a new one on top, then each record over its group and date · Edit · more. */
.mnew { display: grid; gap: var(--sp-2); }
.mnew textarea { width: 100%; }
.mnew__foot { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-2); align-items: center; }
.mnew__foot select { justify-self: start; max-width: 100%; }
.mrow {
display: grid;
grid-template-columns: minmax(0, 1fr) auto var(--control-h-sm);
gap: var(--sp-3);
align-items: center;
min-height: var(--control-h);
padding: var(--sp-2) 0;
border-bottom: var(--border-w) solid var(--border);
}
.mrow.is-editing { grid-template-columns: minmax(0, 1fr); }
.mrow.is-editing textarea { width: 100%; }
.mrow.is-marked { background: var(--surface-active); border-radius: var(--radius-lg); padding-inline: var(--sp-2); }
.mrow__words { display: grid; gap: var(--sp-1); min-width: 0; }
.mrow__text { margin: 0; font: var(--text-sm) / var(--leading-normal) var(--font-body); color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.mrow__sub { margin: 0; font: var(--text-xs) var(--font-ui); color: var(--ink-faint); }
/* --- Narrower windows ---------------------------------------------------------------------- */ /* --- Narrower windows ---------------------------------------------------------------------- */
/* 64rem (--bp-wide): a panel goes over the chat instead of beside it. */ /* 64rem (--bp-wide): a panel goes over the chat instead of beside it. */
+64
View File
@@ -44,6 +44,7 @@ const LEMBAS_I18N = {
"About": "O aplikaci", "About": "O aplikaci",
"Account": "Účet", "Account": "Účet",
"Activity": "Aktivita", "Activity": "Aktivita",
"Add": "Přidat",
"Add folder…": "Přidat složku…", "Add folder…": "Přidat složku…",
"Address of your LLeMbas server": "Adresa vašeho serveru LLeMbas", "Address of your LLeMbas server": "Adresa vašeho serveru LLeMbas",
"Agent": "Agent", "Agent": "Agent",
@@ -105,6 +106,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "Spočítal model při poslední odpovědi; kategorie jsou odhadnuté.", "Counted by the model at the last reply; the categories are estimated.": "Spočítal model při poslední odpovědi; kategorie jsou odhadnuté.",
"Create": "Vytvořit", "Create": "Vytvořit",
"Cut short here; the whole of it is in the web interface.": "Zde zkráceno; celé je to ve webovém rozhraní.", "Cut short here; the whole of it is in the web interface.": "Zde zkráceno; celé je to ve webovém rozhraní.",
"Data group": "Datová skupina",
"Days": "Dny", "Days": "Dny",
"Delete": "Smazat", "Delete": "Smazat",
"Delete chat": "Smazat konverzaci", "Delete chat": "Smazat konverzaci",
@@ -167,6 +169,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — nejdřív se zeptá", "Manual — asks first": "Manual — nejdřív se zeptá",
"Max": "Max", "Max": "Max",
"Maximise": "Maximalizovat", "Maximise": "Maximalizovat",
"Memory": "Paměť",
"Memory and personal": "Paměť a osobní", "Memory and personal": "Paměť a osobní",
"Menu": "Nabídka", "Menu": "Nabídka",
"Message": "Zpráva", "Message": "Zpráva",
@@ -201,10 +204,12 @@ const LEMBAS_I18N = {
"Not measured.": "Neměřeno.", "Not measured.": "Neměřeno.",
"Not run yet": "Ještě neběžela", "Not run yet": "Ještě neběžela",
"Not yet": "Ještě ne", "Not yet": "Ještě ne",
"Noted by a model": "Zaznamenal model",
"Nothing has been run in this shell yet.": "V tomto shellu se zatím nic nespustilo.", "Nothing has been run in this shell yet.": "V tomto shellu se zatím nic nespustilo.",
"Nothing has been said in the room yet.": "V místnosti zatím nikdo nic neřekl.", "Nothing has been said in the room yet.": "V místnosti zatím nikdo nic neřekl.",
"Nothing is scheduled.": "Nic není naplánováno.", "Nothing is scheduled.": "Nic není naplánováno.",
"Nothing matches.": "Nic neodpovídá.", "Nothing matches.": "Nic neodpovídá.",
"Nothing remembered yet.": "Zatím si nic nepamatuje.",
"Nothing running.": "Nic neběží.", "Nothing running.": "Nic neběží.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Na pozadí nic neběží. Podagenti, členové týmu a příkazy na pozadí se tu zobrazí, dokud pracují.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Na pozadí nic neběží. Podagenti, členové týmu a příkazy na pozadí se tu zobrazí, dokud pracují.",
"Nothing to report": "Nic k hlášení", "Nothing to report": "Nic k hlášení",
@@ -245,6 +250,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Vygenerovat odpověď znovu", "Regenerate reply": "Vygenerovat odpověď znovu",
"Remove": "Odebrat", "Remove": "Odebrat",
"Remove %(dir)s": "Odebrat %(dir)s", "Remove %(dir)s": "Odebrat %(dir)s",
"Remove this memory?": "Odstranit tento záznam z paměti?",
"Rename": "Přejmenovat", "Rename": "Přejmenovat",
"Rename chat": "Přejmenovat konverzaci", "Rename chat": "Přejmenovat konverzaci",
"Rename folder": "Přejmenovat složku", "Rename folder": "Přejmenovat složku",
@@ -288,6 +294,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Odhlášením se přihlášení aplikace na serveru zruší; propojení agenta zůstane. Chcete-li použít jiný server, odhlaste se.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Odhlášením se přihlášení aplikace na serveru zruší; propojení agenta zůstane. Chcete-li použít jiný server, odhlaste se.",
"Since the last reply": "Od poslední odpovědi", "Since the last reply": "Od poslední odpovědi",
"Skipped": "Vynecháno", "Skipped": "Vynecháno",
"Something to remember…": "Něco k zapamatování…",
"Source": "Zdroj", "Source": "Zdroj",
"Start the agent": "Spustit agenta", "Start the agent": "Spustit agenta",
"Stop": "Zastavit", "Stop": "Zastavit",
@@ -382,6 +389,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "Čeká se na {{model}}: {{reason}}", "Waiting for {{model}}: {{reason}}": "Čeká se na {{model}}: {{reason}}",
"What it does": "Co dělá", "What it does": "Co dělá",
"What should happen, and when": "Co se má stát a kdy", "What should happen, and when": "Co se má stát a kdy",
"What your models remember about you, across chats. Change or remove anything here.": "Co si o vás vaše modely pamatují napříč konverzacemi. Cokoli tu můžete změnit nebo odstranit.",
"When": "Kdy", "When": "Kdy",
"When it matters": "Když na tom záleží", "When it matters": "Když na tom záleží",
"Writing": "Píše", "Writing": "Píše",
@@ -476,6 +484,7 @@ const LEMBAS_I18N = {
"About": "Über", "About": "Über",
"Account": "Konto", "Account": "Konto",
"Activity": "Aktivität", "Activity": "Aktivität",
"Add": "Hinzufügen",
"Add folder…": "Ordner hinzufügen…", "Add folder…": "Ordner hinzufügen…",
"Address of your LLeMbas server": "Adresse Ihres LLeMbas-Servers", "Address of your LLeMbas server": "Adresse Ihres LLeMbas-Servers",
"Agent": "Agent", "Agent": "Agent",
@@ -537,6 +546,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "Vom Modell bei der letzten Antwort gezählt; die Kategorien sind geschätzt.", "Counted by the model at the last reply; the categories are estimated.": "Vom Modell bei der letzten Antwort gezählt; die Kategorien sind geschätzt.",
"Create": "Erstellen", "Create": "Erstellen",
"Cut short here; the whole of it is in the web interface.": "Hier gekürzt; vollständig steht es in der Weboberfläche.", "Cut short here; the whole of it is in the web interface.": "Hier gekürzt; vollständig steht es in der Weboberfläche.",
"Data group": "Datengruppe",
"Days": "Tage", "Days": "Tage",
"Delete": "Löschen", "Delete": "Löschen",
"Delete chat": "Chat löschen", "Delete chat": "Chat löschen",
@@ -599,6 +609,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — fragt zuerst", "Manual — asks first": "Manual — fragt zuerst",
"Max": "Max", "Max": "Max",
"Maximise": "Maximieren", "Maximise": "Maximieren",
"Memory": "Gedächtnis",
"Memory and personal": "Gedächtnis und Persönliches", "Memory and personal": "Gedächtnis und Persönliches",
"Menu": "Menü", "Menu": "Menü",
"Message": "Nachricht", "Message": "Nachricht",
@@ -633,10 +644,12 @@ const LEMBAS_I18N = {
"Not measured.": "Nicht gemessen.", "Not measured.": "Nicht gemessen.",
"Not run yet": "Noch nicht gelaufen", "Not run yet": "Noch nicht gelaufen",
"Not yet": "Noch nicht", "Not yet": "Noch nicht",
"Noted by a model": "Von einem Modell notiert",
"Nothing has been run in this shell yet.": "In dieser Shell wurde noch nichts ausgeführt.", "Nothing has been run in this shell yet.": "In dieser Shell wurde noch nichts ausgeführt.",
"Nothing has been said in the room yet.": "Im Raum wurde noch nichts gesagt.", "Nothing has been said in the room yet.": "Im Raum wurde noch nichts gesagt.",
"Nothing is scheduled.": "Nichts ist geplant.", "Nothing is scheduled.": "Nichts ist geplant.",
"Nothing matches.": "Nichts gefunden.", "Nothing matches.": "Nichts gefunden.",
"Nothing remembered yet.": "Noch nichts behalten.",
"Nothing running.": "Nichts läuft.", "Nothing running.": "Nichts läuft.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Im Hintergrund läuft nichts. Subagenten, die Mitglieder eines Teams und Hintergrundbefehle erscheinen hier, solange sie arbeiten.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Im Hintergrund läuft nichts. Subagenten, die Mitglieder eines Teams und Hintergrundbefehle erscheinen hier, solange sie arbeiten.",
"Nothing to report": "Nichts zu melden", "Nothing to report": "Nichts zu melden",
@@ -677,6 +690,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Antwort neu generieren", "Regenerate reply": "Antwort neu generieren",
"Remove": "Entfernen", "Remove": "Entfernen",
"Remove %(dir)s": "%(dir)s entfernen", "Remove %(dir)s": "%(dir)s entfernen",
"Remove this memory?": "Diese Erinnerung entfernen?",
"Rename": "Umbenennen", "Rename": "Umbenennen",
"Rename chat": "Chat umbenennen", "Rename chat": "Chat umbenennen",
"Rename folder": "Ordner umbenennen", "Rename folder": "Ordner umbenennen",
@@ -720,6 +734,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Beim Abmelden gibt die App ihre Anmeldung am Server auf; die Verbindung des Agenten bleibt. Melden Sie sich ab, um einen anderen Server zu verwenden.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Beim Abmelden gibt die App ihre Anmeldung am Server auf; die Verbindung des Agenten bleibt. Melden Sie sich ab, um einen anderen Server zu verwenden.",
"Since the last reply": "Seit der letzten Antwort", "Since the last reply": "Seit der letzten Antwort",
"Skipped": "Ausgelassen", "Skipped": "Ausgelassen",
"Something to remember…": "Etwas, das behalten werden soll…",
"Source": "Quelltext", "Source": "Quelltext",
"Start the agent": "Agent starten", "Start the agent": "Agent starten",
"Stop": "Stoppen", "Stop": "Stoppen",
@@ -814,6 +829,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "Warte auf {{model}}: {{reason}}", "Waiting for {{model}}: {{reason}}": "Warte auf {{model}}: {{reason}}",
"What it does": "Was sie tut", "What it does": "Was sie tut",
"What should happen, and when": "Was soll wann geschehen?", "What should happen, and when": "Was soll wann geschehen?",
"What your models remember about you, across chats. Change or remove anything here.": "Was Ihre Modelle über Sie behalten, über alle Chats hinweg. Hier können Sie alles ändern oder entfernen.",
"When": "Wann", "When": "Wann",
"When it matters": "Wenn es zählt", "When it matters": "Wenn es zählt",
"Writing": "Schreibe", "Writing": "Schreibe",
@@ -908,6 +924,7 @@ const LEMBAS_I18N = {
"About": "Acerca de", "About": "Acerca de",
"Account": "Cuenta", "Account": "Cuenta",
"Activity": "Actividad", "Activity": "Actividad",
"Add": "Añadir",
"Add folder…": "Añadir carpeta…", "Add folder…": "Añadir carpeta…",
"Address of your LLeMbas server": "Dirección de su servidor LLeMbas", "Address of your LLeMbas server": "Dirección de su servidor LLeMbas",
"Agent": "Agente", "Agent": "Agente",
@@ -969,6 +986,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "Contado por el modelo en la última respuesta; las categorías son estimaciones.", "Counted by the model at the last reply; the categories are estimated.": "Contado por el modelo en la última respuesta; las categorías son estimaciones.",
"Create": "Crear", "Create": "Crear",
"Cut short here; the whole of it is in the web interface.": "Recortado aquí; completo está en la interfaz web.", "Cut short here; the whole of it is in the web interface.": "Recortado aquí; completo está en la interfaz web.",
"Data group": "Grupo de datos",
"Days": "Días", "Days": "Días",
"Delete": "Eliminar", "Delete": "Eliminar",
"Delete chat": "Eliminar chat", "Delete chat": "Eliminar chat",
@@ -1031,6 +1049,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — pregunta primero", "Manual — asks first": "Manual — pregunta primero",
"Max": "Máx", "Max": "Máx",
"Maximise": "Maximizar", "Maximise": "Maximizar",
"Memory": "Memoria",
"Memory and personal": "Memoria y datos personales", "Memory and personal": "Memoria y datos personales",
"Menu": "Menú", "Menu": "Menú",
"Message": "Mensaje", "Message": "Mensaje",
@@ -1065,10 +1084,12 @@ const LEMBAS_I18N = {
"Not measured.": "Sin medir.", "Not measured.": "Sin medir.",
"Not run yet": "Aún no se ha ejecutado", "Not run yet": "Aún no se ha ejecutado",
"Not yet": "Todavía no", "Not yet": "Todavía no",
"Noted by a model": "Anotado por un modelo",
"Nothing has been run in this shell yet.": "Aún no se ha ejecutado nada en este shell.", "Nothing has been run in this shell yet.": "Aún no se ha ejecutado nada en este shell.",
"Nothing has been said in the room yet.": "Todavía no se ha dicho nada en la sala.", "Nothing has been said in the room yet.": "Todavía no se ha dicho nada en la sala.",
"Nothing is scheduled.": "No hay nada programado.", "Nothing is scheduled.": "No hay nada programado.",
"Nothing matches.": "No hay coincidencias.", "Nothing matches.": "No hay coincidencias.",
"Nothing remembered yet.": "Todavía no se recuerda nada.",
"Nothing running.": "Nada en marcha.", "Nothing running.": "Nada en marcha.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "No hay nada en segundo plano. Los subagentes, los miembros de un equipo y los comandos en segundo plano aparecen aquí mientras trabajan.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "No hay nada en segundo plano. Los subagentes, los miembros de un equipo y los comandos en segundo plano aparecen aquí mientras trabajan.",
"Nothing to report": "Nada que informar", "Nothing to report": "Nada que informar",
@@ -1109,6 +1130,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Regenerar respuesta", "Regenerate reply": "Regenerar respuesta",
"Remove": "Quitar", "Remove": "Quitar",
"Remove %(dir)s": "Quitar %(dir)s", "Remove %(dir)s": "Quitar %(dir)s",
"Remove this memory?": "¿Quitar este recuerdo?",
"Rename": "Renombrar", "Rename": "Renombrar",
"Rename chat": "Renombrar chat", "Rename chat": "Renombrar chat",
"Rename folder": "Renombrar carpeta", "Rename folder": "Renombrar carpeta",
@@ -1152,6 +1174,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Al cerrar sesión, la aplicación renuncia a su acceso en el servidor; la vinculación del agente se mantiene. Cierre sesión para usar otro servidor.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Al cerrar sesión, la aplicación renuncia a su acceso en el servidor; la vinculación del agente se mantiene. Cierre sesión para usar otro servidor.",
"Since the last reply": "Desde la última respuesta", "Since the last reply": "Desde la última respuesta",
"Skipped": "Omitida", "Skipped": "Omitida",
"Something to remember…": "Algo que recordar…",
"Source": "Código fuente", "Source": "Código fuente",
"Start the agent": "Iniciar el agente", "Start the agent": "Iniciar el agente",
"Stop": "Detener", "Stop": "Detener",
@@ -1246,6 +1269,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "Esperando a {{model}}: {{reason}}", "Waiting for {{model}}: {{reason}}": "Esperando a {{model}}: {{reason}}",
"What it does": "Qué hace", "What it does": "Qué hace",
"What should happen, and when": "Qué debe ocurrir, y cuándo", "What should happen, and when": "Qué debe ocurrir, y cuándo",
"What your models remember about you, across chats. Change or remove anything here.": "Lo que sus modelos recuerdan de usted entre chats. Aquí puede cambiar o quitar cualquier cosa.",
"When": "Cuándo", "When": "Cuándo",
"When it matters": "Cuando importa", "When it matters": "Cuando importa",
"Writing": "Escribiendo", "Writing": "Escribiendo",
@@ -1340,6 +1364,7 @@ const LEMBAS_I18N = {
"About": "À propos", "About": "À propos",
"Account": "Compte", "Account": "Compte",
"Activity": "Activité", "Activity": "Activité",
"Add": "Ajouter",
"Add folder…": "Ajouter un dossier…", "Add folder…": "Ajouter un dossier…",
"Address of your LLeMbas server": "Adresse de votre serveur LLeMbas", "Address of your LLeMbas server": "Adresse de votre serveur LLeMbas",
"Agent": "Agent", "Agent": "Agent",
@@ -1401,6 +1426,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "Compté par le modèle lors de la dernière réponse ; les catégories sont estimées.", "Counted by the model at the last reply; the categories are estimated.": "Compté par le modèle lors de la dernière réponse ; les catégories sont estimées.",
"Create": "Créer", "Create": "Créer",
"Cut short here; the whole of it is in the web interface.": "Tronqué ici ; le tout est dans l'interface web.", "Cut short here; the whole of it is in the web interface.": "Tronqué ici ; le tout est dans l'interface web.",
"Data group": "Groupe de données",
"Days": "Jours", "Days": "Jours",
"Delete": "Supprimer", "Delete": "Supprimer",
"Delete chat": "Supprimer la conversation", "Delete chat": "Supprimer la conversation",
@@ -1463,6 +1489,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — demande d'abord", "Manual — asks first": "Manual — demande d'abord",
"Max": "Max", "Max": "Max",
"Maximise": "Agrandir", "Maximise": "Agrandir",
"Memory": "Mémoire",
"Memory and personal": "Mémoire et informations personnelles", "Memory and personal": "Mémoire et informations personnelles",
"Menu": "Menu", "Menu": "Menu",
"Message": "Message", "Message": "Message",
@@ -1497,10 +1524,12 @@ const LEMBAS_I18N = {
"Not measured.": "Non mesuré.", "Not measured.": "Non mesuré.",
"Not run yet": "Pas encore exécutée", "Not run yet": "Pas encore exécutée",
"Not yet": "Pas encore", "Not yet": "Pas encore",
"Noted by a model": "Noté par un modèle",
"Nothing has been run in this shell yet.": "Rien n'a encore été exécuté dans ce shell.", "Nothing has been run in this shell yet.": "Rien n'a encore été exécuté dans ce shell.",
"Nothing has been said in the room yet.": "Rien n’a encore été dit dans la salle.", "Nothing has been said in the room yet.": "Rien n’a encore été dit dans la salle.",
"Nothing is scheduled.": "Rien n’est planifié.", "Nothing is scheduled.": "Rien n’est planifié.",
"Nothing matches.": "Aucun résultat.", "Nothing matches.": "Aucun résultat.",
"Nothing remembered yet.": "Rien n’est encore retenu.",
"Nothing running.": "Rien en cours.", "Nothing running.": "Rien en cours.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Rien ne tourne en arrière-plan. Les sous-agents, les membres d’une équipe et les commandes en arrière-plan s’affichent ici pendant qu’ils travaillent.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Rien ne tourne en arrière-plan. Les sous-agents, les membres d’une équipe et les commandes en arrière-plan s’affichent ici pendant qu’ils travaillent.",
"Nothing to report": "Rien à signaler", "Nothing to report": "Rien à signaler",
@@ -1541,6 +1570,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Régénérer la réponse", "Regenerate reply": "Régénérer la réponse",
"Remove": "Retirer", "Remove": "Retirer",
"Remove %(dir)s": "Retirer %(dir)s", "Remove %(dir)s": "Retirer %(dir)s",
"Remove this memory?": "Supprimer ce souvenir ?",
"Rename": "Renommer", "Rename": "Renommer",
"Rename chat": "Renommer la conversation", "Rename chat": "Renommer la conversation",
"Rename folder": "Renommer le dossier", "Rename folder": "Renommer le dossier",
@@ -1584,6 +1614,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "La déconnexion abandonne la connexion de l'application au serveur ; la liaison de l'agent reste. Déconnectez-vous pour utiliser un autre serveur.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "La déconnexion abandonne la connexion de l'application au serveur ; la liaison de l'agent reste. Déconnectez-vous pour utiliser un autre serveur.",
"Since the last reply": "Depuis la dernière réponse", "Since the last reply": "Depuis la dernière réponse",
"Skipped": "Ignorée", "Skipped": "Ignorée",
"Something to remember…": "Quelque chose à retenir…",
"Source": "Source", "Source": "Source",
"Start the agent": "Démarrer l'agent", "Start the agent": "Démarrer l'agent",
"Stop": "Arrêter", "Stop": "Arrêter",
@@ -1678,6 +1709,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "En attente de {{model}} : {{reason}}", "Waiting for {{model}}: {{reason}}": "En attente de {{model}} : {{reason}}",
"What it does": "Ce qu’elle fait", "What it does": "Ce qu’elle fait",
"What should happen, and when": "Ce qui doit se passer, et quand", "What should happen, and when": "Ce qui doit se passer, et quand",
"What your models remember about you, across chats. Change or remove anything here.": "Ce que vos modèles retiennent de vous, d’une conversation à l’autre. Modifiez ou supprimez ce que vous voulez ici.",
"When": "Quand", "When": "Quand",
"When it matters": "Quand c'est important", "When it matters": "Quand c'est important",
"Writing": "Écriture", "Writing": "Écriture",
@@ -1772,6 +1804,7 @@ const LEMBAS_I18N = {
"About": "Névjegy", "About": "Névjegy",
"Account": "Fiók", "Account": "Fiók",
"Activity": "Tevékenység", "Activity": "Tevékenység",
"Add": "Hozzáadás",
"Add folder…": "Mappa hozzáadása…", "Add folder…": "Mappa hozzáadása…",
"Address of your LLeMbas server": "A LLeMbas-kiszolgáló címe", "Address of your LLeMbas server": "A LLeMbas-kiszolgáló címe",
"Agent": "Ügynök", "Agent": "Ügynök",
@@ -1833,6 +1866,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "A modell számolta az utolsó válasznál; a kategóriák becslések.", "Counted by the model at the last reply; the categories are estimated.": "A modell számolta az utolsó válasznál; a kategóriák becslések.",
"Create": "Létrehozás", "Create": "Létrehozás",
"Cut short here; the whole of it is in the web interface.": "Itt rövidítve; a teljes a webes felületen van.", "Cut short here; the whole of it is in the web interface.": "Itt rövidítve; a teljes a webes felületen van.",
"Data group": "Adatcsoport",
"Days": "Napok", "Days": "Napok",
"Delete": "Törlés", "Delete": "Törlés",
"Delete chat": "Csevegés törlése", "Delete chat": "Csevegés törlése",
@@ -1895,6 +1929,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — előbb kérdez", "Manual — asks first": "Manual — előbb kérdez",
"Max": "Max", "Max": "Max",
"Maximise": "Teljes méret", "Maximise": "Teljes méret",
"Memory": "Memória",
"Memory and personal": "Memória és személyes adatok", "Memory and personal": "Memória és személyes adatok",
"Menu": "Menü", "Menu": "Menü",
"Message": "Üzenet", "Message": "Üzenet",
@@ -1929,10 +1964,12 @@ const LEMBAS_I18N = {
"Not measured.": "Nincs mérve.", "Not measured.": "Nincs mérve.",
"Not run yet": "Még nem futott", "Not run yet": "Még nem futott",
"Not yet": "Még nem", "Not yet": "Még nem",
"Noted by a model": "Egy modell jegyezte fel",
"Nothing has been run in this shell yet.": "Ebben a shellben még semmi sem futott.", "Nothing has been run in this shell yet.": "Ebben a shellben még semmi sem futott.",
"Nothing has been said in the room yet.": "A szobában még senki nem mondott semmit.", "Nothing has been said in the room yet.": "A szobában még senki nem mondott semmit.",
"Nothing is scheduled.": "Nincs semmi ütemezve.", "Nothing is scheduled.": "Nincs semmi ütemezve.",
"Nothing matches.": "Nincs találat.", "Nothing matches.": "Nincs találat.",
"Nothing remembered yet.": "Még nincs megjegyezve semmi.",
"Nothing running.": "Semmi sem fut.", "Nothing running.": "Semmi sem fut.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "A háttérben semmi sem fut. Az alügynökök, egy csapat tagjai és a háttérparancsok itt jelennek meg, amíg dolgoznak.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "A háttérben semmi sem fut. Az alügynökök, egy csapat tagjai és a háttérparancsok itt jelennek meg, amíg dolgoznak.",
"Nothing to report": "Nincs jelentenivaló", "Nothing to report": "Nincs jelentenivaló",
@@ -1973,6 +2010,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Válasz újragenerálása", "Regenerate reply": "Válasz újragenerálása",
"Remove": "Eltávolítás", "Remove": "Eltávolítás",
"Remove %(dir)s": "%(dir)s eltávolítása", "Remove %(dir)s": "%(dir)s eltávolítása",
"Remove this memory?": "Eltávolítja ezt az emléket?",
"Rename": "Átnevezés", "Rename": "Átnevezés",
"Rename chat": "Csevegés átnevezése", "Rename chat": "Csevegés átnevezése",
"Rename folder": "Mappa átnevezése", "Rename folder": "Mappa átnevezése",
@@ -2016,6 +2054,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Kijelentkezéskor az alkalmazás lemond a szerveren lévő bejelentkezéséről; az ügynök kapcsolata megmarad. Más szerver használatához jelentkezzen ki.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Kijelentkezéskor az alkalmazás lemond a szerveren lévő bejelentkezéséről; az ügynök kapcsolata megmarad. Más szerver használatához jelentkezzen ki.",
"Since the last reply": "Az utolsó válasz óta", "Since the last reply": "Az utolsó válasz óta",
"Skipped": "Kihagyva", "Skipped": "Kihagyva",
"Something to remember…": "Valami, amit meg kell jegyezni…",
"Source": "Forrás", "Source": "Forrás",
"Start the agent": "Ügynök indítása", "Start the agent": "Ügynök indítása",
"Stop": "Leállítás", "Stop": "Leállítás",
@@ -2110,6 +2149,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "Várakozás a(z) {{model}} modellre: {{reason}}", "Waiting for {{model}}: {{reason}}": "Várakozás a(z) {{model}} modellre: {{reason}}",
"What it does": "Mit csinál", "What it does": "Mit csinál",
"What should happen, and when": "Mi történjen, és mikor", "What should happen, and when": "Mi történjen, és mikor",
"What your models remember about you, across chats. Change or remove anything here.": "Amit a modelljei megjegyeznek Önről a beszélgetéseken át. Itt bármit módosíthat vagy eltávolíthat.",
"When": "Mikor", "When": "Mikor",
"When it matters": "Ha számít", "When it matters": "Ha számít",
"Writing": "Írás", "Writing": "Írás",
@@ -2204,6 +2244,7 @@ const LEMBAS_I18N = {
"About": "Informazioni", "About": "Informazioni",
"Account": "Account", "Account": "Account",
"Activity": "Attività", "Activity": "Attività",
"Add": "Aggiungi",
"Add folder…": "Aggiungi cartella…", "Add folder…": "Aggiungi cartella…",
"Address of your LLeMbas server": "Indirizzo del tuo server LLeMbas", "Address of your LLeMbas server": "Indirizzo del tuo server LLeMbas",
"Agent": "Agente", "Agent": "Agente",
@@ -2265,6 +2306,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "Contato dal modello all’ultima risposta; le categorie sono stime.", "Counted by the model at the last reply; the categories are estimated.": "Contato dal modello all’ultima risposta; le categorie sono stime.",
"Create": "Crea", "Create": "Crea",
"Cut short here; the whole of it is in the web interface.": "Troncato qui; il resto è nell'interfaccia web.", "Cut short here; the whole of it is in the web interface.": "Troncato qui; il resto è nell'interfaccia web.",
"Data group": "Gruppo di dati",
"Days": "Giorni", "Days": "Giorni",
"Delete": "Elimina", "Delete": "Elimina",
"Delete chat": "Elimina chat", "Delete chat": "Elimina chat",
@@ -2327,6 +2369,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — chiede prima", "Manual — asks first": "Manual — chiede prima",
"Max": "Max", "Max": "Max",
"Maximise": "Ingrandisci", "Maximise": "Ingrandisci",
"Memory": "Memoria",
"Memory and personal": "Memoria e dati personali", "Memory and personal": "Memoria e dati personali",
"Menu": "Menu", "Menu": "Menu",
"Message": "Messaggio", "Message": "Messaggio",
@@ -2361,10 +2404,12 @@ const LEMBAS_I18N = {
"Not measured.": "Non misurato.", "Not measured.": "Non misurato.",
"Not run yet": "Non ancora eseguita", "Not run yet": "Non ancora eseguita",
"Not yet": "Non ancora", "Not yet": "Non ancora",
"Noted by a model": "Annotato da un modello",
"Nothing has been run in this shell yet.": "In questa shell non è ancora stato eseguito nulla.", "Nothing has been run in this shell yet.": "In questa shell non è ancora stato eseguito nulla.",
"Nothing has been said in the room yet.": "Nella stanza non è ancora stato detto nulla.", "Nothing has been said in the room yet.": "Nella stanza non è ancora stato detto nulla.",
"Nothing is scheduled.": "Non c’è niente di pianificato.", "Nothing is scheduled.": "Non c’è niente di pianificato.",
"Nothing matches.": "Nessun risultato.", "Nothing matches.": "Nessun risultato.",
"Nothing remembered yet.": "Ancora niente da ricordare.",
"Nothing running.": "Nulla in esecuzione.", "Nothing running.": "Nulla in esecuzione.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Nulla è in esecuzione in background. Sottoagenti, membri di una squadra e comandi in background compaiono qui mentre lavorano.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Nulla è in esecuzione in background. Sottoagenti, membri di una squadra e comandi in background compaiono qui mentre lavorano.",
"Nothing to report": "Niente da segnalare", "Nothing to report": "Niente da segnalare",
@@ -2405,6 +2450,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Rigenera risposta", "Regenerate reply": "Rigenera risposta",
"Remove": "Rimuovi", "Remove": "Rimuovi",
"Remove %(dir)s": "Rimuovi %(dir)s", "Remove %(dir)s": "Rimuovi %(dir)s",
"Remove this memory?": "Rimuovere questo ricordo?",
"Rename": "Rinomina", "Rename": "Rinomina",
"Rename chat": "Rinomina chat", "Rename chat": "Rinomina chat",
"Rename folder": "Rinomina cartella", "Rename folder": "Rinomina cartella",
@@ -2448,6 +2494,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Uscendo, l'app rinuncia al suo accesso sul server; il collegamento dell'agente resta. Esca per usare un altro server.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Uscendo, l'app rinuncia al suo accesso sul server; il collegamento dell'agente resta. Esca per usare un altro server.",
"Since the last reply": "Dall’ultima risposta", "Since the last reply": "Dall’ultima risposta",
"Skipped": "Saltata", "Skipped": "Saltata",
"Something to remember…": "Qualcosa da ricordare…",
"Source": "Sorgente", "Source": "Sorgente",
"Start the agent": "Avvia l'agente", "Start the agent": "Avvia l'agente",
"Stop": "Ferma", "Stop": "Ferma",
@@ -2542,6 +2589,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "In attesa di {{model}}: {{reason}}", "Waiting for {{model}}: {{reason}}": "In attesa di {{model}}: {{reason}}",
"What it does": "Cosa fa", "What it does": "Cosa fa",
"What should happen, and when": "Cosa deve succedere, e quando", "What should happen, and when": "Cosa deve succedere, e quando",
"What your models remember about you, across chats. Change or remove anything here.": "Ciò che i tuoi modelli ricordano di te tra una chat e l’altra. Qui puoi cambiare o rimuovere qualsiasi cosa.",
"When": "Quando", "When": "Quando",
"When it matters": "Quando conta", "When it matters": "Quando conta",
"Writing": "Scrittura", "Writing": "Scrittura",
@@ -2636,6 +2684,7 @@ const LEMBAS_I18N = {
"About": "O aplikacji", "About": "O aplikacji",
"Account": "Konto", "Account": "Konto",
"Activity": "Aktywność", "Activity": "Aktywność",
"Add": "Dodaj",
"Add folder…": "Dodaj folder…", "Add folder…": "Dodaj folder…",
"Address of your LLeMbas server": "Adres Twojego serwera LLeMbas", "Address of your LLeMbas server": "Adres Twojego serwera LLeMbas",
"Agent": "Agent", "Agent": "Agent",
@@ -2697,6 +2746,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "Policzone przez model przy ostatniej odpowiedzi; kategorie są szacunkowe.", "Counted by the model at the last reply; the categories are estimated.": "Policzone przez model przy ostatniej odpowiedzi; kategorie są szacunkowe.",
"Create": "Utwórz", "Create": "Utwórz",
"Cut short here; the whole of it is in the web interface.": "Tu skrócone; całość jest w interfejsie WWW.", "Cut short here; the whole of it is in the web interface.": "Tu skrócone; całość jest w interfejsie WWW.",
"Data group": "Grupa danych",
"Days": "Dni", "Days": "Dni",
"Delete": "Usuń", "Delete": "Usuń",
"Delete chat": "Usuń czat", "Delete chat": "Usuń czat",
@@ -2759,6 +2809,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — najpierw pyta", "Manual — asks first": "Manual — najpierw pyta",
"Max": "Max", "Max": "Max",
"Maximise": "Maksymalizuj", "Maximise": "Maksymalizuj",
"Memory": "Pamięć",
"Memory and personal": "Pamięć i dane osobiste", "Memory and personal": "Pamięć i dane osobiste",
"Menu": "Menu", "Menu": "Menu",
"Message": "Wiadomość", "Message": "Wiadomość",
@@ -2793,10 +2844,12 @@ const LEMBAS_I18N = {
"Not measured.": "Nie zmierzono.", "Not measured.": "Nie zmierzono.",
"Not run yet": "Jeszcze nie uruchomione", "Not run yet": "Jeszcze nie uruchomione",
"Not yet": "Jeszcze nie", "Not yet": "Jeszcze nie",
"Noted by a model": "Zanotowane przez model",
"Nothing has been run in this shell yet.": "W tej powłoce nic jeszcze nie uruchomiono.", "Nothing has been run in this shell yet.": "W tej powłoce nic jeszcze nie uruchomiono.",
"Nothing has been said in the room yet.": "W pokoju nikt jeszcze nic nie powiedział.", "Nothing has been said in the room yet.": "W pokoju nikt jeszcze nic nie powiedział.",
"Nothing is scheduled.": "Nic nie jest zaplanowane.", "Nothing is scheduled.": "Nic nie jest zaplanowane.",
"Nothing matches.": "Nic nie pasuje.", "Nothing matches.": "Nic nie pasuje.",
"Nothing remembered yet.": "Na razie nic nie zapamiętano.",
"Nothing running.": "Nic nie działa.", "Nothing running.": "Nic nie działa.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "W tle nic nie działa. Podagenci, członkowie zespołu i polecenia w tle pojawiają się tu, gdy pracują.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "W tle nic nie działa. Podagenci, członkowie zespołu i polecenia w tle pojawiają się tu, gdy pracują.",
"Nothing to report": "Nic do zgłoszenia", "Nothing to report": "Nic do zgłoszenia",
@@ -2837,6 +2890,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Wygeneruj odpowiedź ponownie", "Regenerate reply": "Wygeneruj odpowiedź ponownie",
"Remove": "Usuń", "Remove": "Usuń",
"Remove %(dir)s": "Usuń %(dir)s", "Remove %(dir)s": "Usuń %(dir)s",
"Remove this memory?": "Usunąć to wspomnienie?",
"Rename": "Zmień nazwę", "Rename": "Zmień nazwę",
"Rename chat": "Zmień nazwę czatu", "Rename chat": "Zmień nazwę czatu",
"Rename folder": "Zmień nazwę folderu", "Rename folder": "Zmień nazwę folderu",
@@ -2880,6 +2934,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Wylogowanie zrzeka się logowania aplikacji na serwerze; połączenie agenta zostaje. Wyloguj się, aby użyć innego serwera.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Wylogowanie zrzeka się logowania aplikacji na serwerze; połączenie agenta zostaje. Wyloguj się, aby użyć innego serwera.",
"Since the last reply": "Od ostatniej odpowiedzi", "Since the last reply": "Od ostatniej odpowiedzi",
"Skipped": "Pominięty", "Skipped": "Pominięty",
"Something to remember…": "Coś do zapamiętania…",
"Source": "Źródło", "Source": "Źródło",
"Start the agent": "Uruchom agenta", "Start the agent": "Uruchom agenta",
"Stop": "Zatrzymać", "Stop": "Zatrzymać",
@@ -2974,6 +3029,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "Czekam na {{model}}: {{reason}}", "Waiting for {{model}}: {{reason}}": "Czekam na {{model}}: {{reason}}",
"What it does": "Co robi", "What it does": "Co robi",
"What should happen, and when": "Co ma się stać i kiedy", "What should happen, and when": "Co ma się stać i kiedy",
"What your models remember about you, across chats. Change or remove anything here.": "Co Twoje modele pamiętają o Tobie we wszystkich czatach. Tutaj możesz wszystko zmienić lub usunąć.",
"When": "Kiedy", "When": "Kiedy",
"When it matters": "Gdy to ważne", "When it matters": "Gdy to ważne",
"Writing": "Pisanie", "Writing": "Pisanie",
@@ -3068,6 +3124,7 @@ const LEMBAS_I18N = {
"About": "O aplikácii", "About": "O aplikácii",
"Account": "Účet", "Account": "Účet",
"Activity": "Aktivita", "Activity": "Aktivita",
"Add": "Pridať",
"Add folder…": "Pridať priečinok…", "Add folder…": "Pridať priečinok…",
"Address of your LLeMbas server": "Adresa vášho servera LLeMbas", "Address of your LLeMbas server": "Adresa vášho servera LLeMbas",
"Agent": "Agent", "Agent": "Agent",
@@ -3129,6 +3186,7 @@ const LEMBAS_I18N = {
"Counted by the model at the last reply; the categories are estimated.": "Spočítal model pri poslednej odpovedi; kategórie sú odhadnuté.", "Counted by the model at the last reply; the categories are estimated.": "Spočítal model pri poslednej odpovedi; kategórie sú odhadnuté.",
"Create": "Vytvoriť", "Create": "Vytvoriť",
"Cut short here; the whole of it is in the web interface.": "Tu skrátené; celé je to vo webovom rozhraní.", "Cut short here; the whole of it is in the web interface.": "Tu skrátené; celé je to vo webovom rozhraní.",
"Data group": "Dátová oblasť",
"Days": "Dni", "Days": "Dni",
"Delete": "Zmazať", "Delete": "Zmazať",
"Delete chat": "Zmazať konverzáciu", "Delete chat": "Zmazať konverzáciu",
@@ -3191,6 +3249,7 @@ const LEMBAS_I18N = {
"Manual — asks first": "Manual — najprv sa opýta", "Manual — asks first": "Manual — najprv sa opýta",
"Max": "Max", "Max": "Max",
"Maximise": "Maximalizovať", "Maximise": "Maximalizovať",
"Memory": "Pamäť",
"Memory and personal": "Pamäť a osobné", "Memory and personal": "Pamäť a osobné",
"Menu": "Ponuka", "Menu": "Ponuka",
"Message": "Správa", "Message": "Správa",
@@ -3225,10 +3284,12 @@ const LEMBAS_I18N = {
"Not measured.": "Nezmerané.", "Not measured.": "Nezmerané.",
"Not run yet": "Ešte nebežala", "Not run yet": "Ešte nebežala",
"Not yet": "Ešte nie", "Not yet": "Ešte nie",
"Noted by a model": "Zaznamenal model",
"Nothing has been run in this shell yet.": "V tomto shelli sa zatiaľ nič nespustilo.", "Nothing has been run in this shell yet.": "V tomto shelli sa zatiaľ nič nespustilo.",
"Nothing has been said in the room yet.": "V miestnosti zatiaľ nikto nič nepovedal.", "Nothing has been said in the room yet.": "V miestnosti zatiaľ nikto nič nepovedal.",
"Nothing is scheduled.": "Nič nie je naplánované.", "Nothing is scheduled.": "Nič nie je naplánované.",
"Nothing matches.": "Nič nezodpovedá.", "Nothing matches.": "Nič nezodpovedá.",
"Nothing remembered yet.": "Zatiaľ si nič nepamätá.",
"Nothing running.": "Nič nebeží.", "Nothing running.": "Nič nebeží.",
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Na pozadí nič nebeží. Podagenti, členovia tímu a príkazy na pozadí sa tu zobrazia, kým pracujú.", "Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Na pozadí nič nebeží. Podagenti, členovia tímu a príkazy na pozadí sa tu zobrazia, kým pracujú.",
"Nothing to report": "Nič na hlásenie", "Nothing to report": "Nič na hlásenie",
@@ -3269,6 +3330,7 @@ const LEMBAS_I18N = {
"Regenerate reply": "Vygenerovať odpoveď znova", "Regenerate reply": "Vygenerovať odpoveď znova",
"Remove": "Odstrániť", "Remove": "Odstrániť",
"Remove %(dir)s": "Odstrániť %(dir)s", "Remove %(dir)s": "Odstrániť %(dir)s",
"Remove this memory?": "Odstrániť tento záznam z pamäte?",
"Rename": "Premenovať", "Rename": "Premenovať",
"Rename chat": "Premenovať konverzáciu", "Rename chat": "Premenovať konverzáciu",
"Rename folder": "Premenovať zložku", "Rename folder": "Premenovať zložku",
@@ -3312,6 +3374,7 @@ const LEMBAS_I18N = {
"Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Odhlásením sa prihlásenie aplikácie na serveri zruší; prepojenie agenta zostane. Ak chcete použiť iný server, odhláste sa.", "Signing out gives the app's sign-in up on the server; the agent's link stays. Sign out to use another server.": "Odhlásením sa prihlásenie aplikácie na serveri zruší; prepojenie agenta zostane. Ak chcete použiť iný server, odhláste sa.",
"Since the last reply": "Od poslednej odpovede", "Since the last reply": "Od poslednej odpovede",
"Skipped": "Vynechané", "Skipped": "Vynechané",
"Something to remember…": "Niečo na zapamätanie…",
"Source": "Zdroj", "Source": "Zdroj",
"Start the agent": "Spustiť agenta", "Start the agent": "Spustiť agenta",
"Stop": "Zastaviť", "Stop": "Zastaviť",
@@ -3406,6 +3469,7 @@ const LEMBAS_I18N = {
"Waiting for {{model}}: {{reason}}": "Čaká sa na {{model}}: {{reason}}", "Waiting for {{model}}: {{reason}}": "Čaká sa na {{model}}: {{reason}}",
"What it does": "Čo robí", "What it does": "Čo robí",
"What should happen, and when": "Čo sa má stať a kedy", "What should happen, and when": "Čo sa má stať a kedy",
"What your models remember about you, across chats. Change or remove anything here.": "Čo si o vás vaše modely pamätajú naprieč konverzáciami. Čokoľvek tu môžete zmeniť alebo odstrániť.",
"When": "Kedy", "When": "Kedy",
"When it matters": "Keď na tom záleží", "When it matters": "Keď na tom záleží",
"Writing": "Píše", "Writing": "Píše",
+10
View File
@@ -54,6 +54,7 @@
<button type="button" class="navrow" id="nav-scheduled" data-view="scheduled"><span class="navrow__icon"></span><span data-i18n>Scheduled</span></button> <button type="button" class="navrow" id="nav-scheduled" data-view="scheduled"><span class="navrow__icon"></span><span data-i18n>Scheduled</span></button>
<button type="button" class="navrow" id="nav-files" data-view="files"><span class="navrow__icon"></span><span data-i18n>Files</span></button> <button type="button" class="navrow" id="nav-files" data-view="files"><span class="navrow__icon"></span><span data-i18n>Files</span></button>
<button type="button" class="navrow" id="nav-archived" data-view="archived"><span class="navrow__icon"></span><span data-i18n>Archived</span></button> <button type="button" class="navrow" id="nav-archived" data-view="archived"><span class="navrow__icon"></span><span data-i18n>Archived</span></button>
<button type="button" class="navrow" id="nav-memory" data-view="memory"><span class="navrow__icon"></span><span data-i18n>Memory</span></button>
</div> </div>
<ul class="list" id="chat-list" aria-label="Chats" data-i18n-aria-label></ul> <ul class="list" id="chat-list" aria-label="Chats" data-i18n-aria-label></ul>
<div class="side__foot"> <div class="side__foot">
@@ -133,6 +134,15 @@
<div class="page__body" id="archived-body"></div> <div class="page__body" id="archived-body"></div>
</section> </section>
<section class="page" id="page-memory" hidden aria-labelledby="memory-title">
<header class="head">
<h1 class="head__title" id="memory-title" data-i18n>Memory</h1>
<div class="head__place"></div>
<div class="head__tools"><button type="button" class="tool" id="memory-refresh" aria-label="Read again" title="Read again" data-i18n-aria-label data-i18n-title></button></div>
</header>
<div class="page__body" id="memory-body"></div>
</section>
<section class="page settings" id="settings" hidden aria-labelledby="settings-title"> <section class="page settings" id="settings" hidden aria-labelledby="settings-title">
<header class="head"> <header class="head">
<h1 class="head__title" id="settings-title" data-i18n>Settings</h1> <h1 class="head__title" id="settings-title" data-i18n>Settings</h1>
+20 -3
View File
@@ -10,7 +10,7 @@ import { renderBar, initBar } from "./titlebar.js"
import { renderSetup, initSetup } from "./setup.js" import { renderSetup, initSetup } from "./setup.js"
import { renderSettings, initSettings, scrollToSection } from "./settings.js" import { renderSettings, initSettings, scrollToSection } from "./settings.js"
import { initViewer, onViewer, closeViewer } from "./viewer.js" import { initViewer, onViewer, closeViewer } from "./viewer.js"
import { initExplorer, showLocal, showFilesTab } from "./explorer.js" import { initExplorer, showLocal, showFilesTab, openServerFile } from "./explorer.js"
import { initList, refreshList, markCurrent } from "./list.js" import { initList, refreshList, markCurrent } from "./list.js"
import { initChat, openChat, newChat, closeChat, openChatId, chatMenu, statusChanged, draftKind, onThisComputer, modelsChanged, intoComposer } from "./chat.js" import { initChat, openChat, newChat, closeChat, openChatId, chatMenu, statusChanged, draftKind, onThisComputer, modelsChanged, intoComposer } from "./chat.js"
import { initTerminal, showTerminal, terminalOpen, terminalAvailable, toggleTerminal } from "./terminal.js" import { initTerminal, showTerminal, terminalOpen, terminalAvailable, toggleTerminal } from "./terminal.js"
@@ -21,12 +21,13 @@ import { initActivity, showPanel, togglePanel, panelOpen } from "./activity.js"
import { initAgent, link } from "./agent.js" import { initAgent, link } from "./agent.js"
import { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js" import { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js"
import { initArchived, showArchived } from "./archived.js" import { initArchived, showArchived } from "./archived.js"
import { showMemory } from "./memory.js"
import { closePopover } from "./popup.js" import { closePopover } from "./popup.js"
import { noteSubagentsCap } from "./models.js" import { noteSubagentsCap } from "./models.js"
let status = null let status = null
let view = "" // "setup" | "app" let view = "" // "setup" | "app"
let page = "chat" // "chat" | "files" | "scheduled" | "archived" | "settings" let page = "chat" // "chat" | "files" | "scheduled" | "archived" | "memory" | "settings"
let me = null let me = null
let models = [] let models = []
let devices = [] // this account's linked devices let devices = [] // this account's linked devices
@@ -156,6 +157,7 @@ function showPage(name) {
$("page-files").hidden = name !== "files" $("page-files").hidden = name !== "files"
$("page-scheduled").hidden = name !== "scheduled" $("page-scheduled").hidden = name !== "scheduled"
$("page-archived").hidden = name !== "archived" $("page-archived").hidden = name !== "archived"
$("page-memory").hidden = name !== "memory"
$("settings").hidden = name !== "settings" $("settings").hidden = name !== "settings"
for (const b of document.querySelectorAll(".navrow[data-view]")) b.setAttribute("aria-current", String(b.dataset.view === name)) for (const b of document.querySelectorAll(".navrow[data-view]")) b.setAttribute("aria-current", String(b.dataset.view === name))
if (name !== "chat") { if (name !== "chat") {
@@ -208,6 +210,11 @@ function showArchivedPage() {
showArchived() showArchived()
} }
function showMemoryPage(focus) {
showPage("memory")
showMemory(focus || "")
}
function showFiles(which) { function showFiles(which) {
showPage("files") showPage("files")
if (which) showFilesTab(which) if (which) showFilesTab(which)
@@ -223,6 +230,7 @@ function commands() {
...scheduledCommands(), ...scheduledCommands(),
{ label: t("Files"), run: () => showFiles() }, { label: t("Files"), run: () => showFiles() },
{ label: t("Archived"), run: showArchivedPage }, { label: t("Archived"), run: showArchivedPage },
{ label: t("Memory"), run: () => showMemoryPage() },
{ label: t("Settings"), run: () => showSettings(true) }, { label: t("Settings"), run: () => showSettings(true) },
] ]
if (openChatId()) out.push({ label: panelOpen() ? t("Close the activity panel") : t("Open the activity panel"), run: togglePanel }) if (openChatId()) out.push({ label: panelOpen() ? t("Close the activity panel") : t("Open the activity panel"), run: togglePanel })
@@ -301,7 +309,12 @@ async function boot() {
// Back in the sidebar: the list again. // Back in the sidebar: the list again.
changed: () => refreshList(), changed: () => refreshList(),
}) })
initPalette({ commands, open: (c) => showChat(c.id) }) initPalette({
commands,
open: (c) => showChat(c.id),
openFile: (path, name) => openServerFile(path, name),
openMemory: (id) => showMemoryPage(id),
})
initScheduled({ status: () => status, models: () => models, me: () => me, openChat: (id) => showChat(id), show: () => showPage("scheduled") }) initScheduled({ status: () => status, models: () => models, me: () => me, openChat: (id) => showChat(id), show: () => showPage("scheduled") })
onViewer((on) => { onViewer((on) => {
$("shell").dataset.viewer = String(on) $("shell").dataset.viewer = String(on)
@@ -340,6 +353,10 @@ async function boot() {
$("nav-archived").addEventListener("click", showArchivedPage) $("nav-archived").addEventListener("click", showArchivedPage)
$("archived-refresh").replaceChildren(icon(ICONS.refresh)) $("archived-refresh").replaceChildren(icon(ICONS.refresh))
$("archived-refresh").addEventListener("click", () => showArchived()) $("archived-refresh").addEventListener("click", () => showArchived())
$("nav-memory").querySelector(".navrow__icon").replaceChildren(icon(ICONS.bulb))
$("nav-memory").addEventListener("click", () => showMemoryPage())
$("memory-refresh").replaceChildren(icon(ICONS.refresh))
$("memory-refresh").addEventListener("click", () => showMemory())
accountButton() accountButton()
$("settings-btn").addEventListener("click", () => showSettings(page !== "settings")) $("settings-btn").addEventListener("click", () => showSettings(page !== "settings"))
$("settings-close").replaceChildren(icon(ICONS.close)) $("settings-close").replaceChildren(icon(ICONS.close))
+159
View File
@@ -0,0 +1,159 @@
// Memory: what the account's models remember about the person, as the web's Settings → Memory
// keeps it. Each record can be changed or removed; a new one is added at the top, into the data
// group chosen (where there is more than one).
//
// GET /memories { memories: [memory], groups: [{ id, name }] }
// POST /memories { content, data_group_id? } -> { memory }
// PATCH /memories/{id} { content } -> { memory }
// DELETE /memories/{id} 204
//
// A record may be a model's words (`author` other than "user"): set as text, never markup.
import { $, h, clear, icon, ICONS, when } from "./dom.js"
import { get, post, patch, del, why } from "./bridge.js"
import { ask, menu } from "./popup.js"
let rows = []
let groups = []
let error = ""
let loading = false
let editing = "" // the record being changed
let mark = "" // the record to bring into view (from a search hit)
/** The page, read again from the server; `focus` a record to bring into view. */
export async function showMemory(focus = "") {
mark = focus
loading = true
error = ""
render()
try {
const r = await get("/memories")
rows = r.memories || []
groups = r.groups || []
} catch (err) {
rows = []
error = why(err)
}
loading = false
render()
}
async function add(form) {
const text = form.querySelector("textarea")
const content = text.value.trim()
if (!content) return text.focus()
const group = form.querySelector("select")?.value
form.querySelector("button[type=submit]").disabled = true
try {
const r = await post("/memories", group ? { content, data_group_id: group } : { content })
if (r?.memory) rows = [r.memory, ...rows]
error = ""
text.value = ""
} catch (err) {
error = why(err)
}
render()
$("memory-new")?.focus()
}
async function save(m, content) {
content = content.trim()
if (!content || content === m.content) {
editing = ""
return render()
}
try {
const r = await patch(`/memories/${encodeURIComponent(m.id)}`, { content })
rows = rows.map((x) => (x.id === m.id ? r?.memory || { ...x, content } : x))
editing = ""
error = ""
} catch (err) {
error = why(err)
}
render()
}
async function remove(m) {
const yes = await ask({ title: t("Remove this memory?"), text: m.content, ok: t("Remove"), cancel: t("Cancel"), danger: true })
if (!yes) return
try {
await del(`/memories/${encodeURIComponent(m.id)}`)
rows = rows.filter((x) => x.id !== m.id)
error = ""
} catch (err) {
error = why(err)
}
render()
}
function row(m) {
const sub = [groups.length > 1 ? m.data_group?.name : "", m.author && m.author !== "user" ? t("Noted by a model") : "", when(m.updated_at || m.created_at)].filter(Boolean).join(" · ")
if (editing === m.id) {
const box = h("textarea", { class: "textarea mrow__edit", rows: "3", "aria-label": t("Memory") })
box.value = m.content
const done = () => save(m, box.value)
box.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
e.stopPropagation()
editing = ""
render()
}
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) done()
})
queueMicrotask(() => box.focus())
return h(
"li",
{ class: "mrow is-editing", dataset: { id: m.id } },
box,
h(
"div",
{ class: "set__buttons" },
h("button", { type: "button", class: "btn btn--sm", text: t("Cancel"), onclick: () => ((editing = ""), render()) }),
h("button", { type: "button", class: "btn btn--sm btn--primary", text: t("Save"), onclick: done }),
),
)
}
return h(
"li",
{ class: `mrow${mark === m.id ? " is-marked" : ""}`, dataset: { id: m.id } },
h("div", { class: "mrow__words" }, h("p", { class: "mrow__text" }, h("bdi", { text: m.content })), h("p", { class: "mrow__sub", text: sub })),
h("button", { type: "button", class: "btn btn--sm", text: t("Edit"), onclick: () => ((editing = m.id), render()) }),
h(
"button",
{ type: "button", class: "tool", "aria-label": t("More"), title: t("More"), onclick: (e) => menu(e.currentTarget, [{ label: t("Remove"), danger: true, run: () => remove(m) }]) },
icon(ICONS.more),
),
)
}
function render() {
const box = clear($("memory-body"))
const parts = [h("p", { class: "muted", text: t("What your models remember about you, across chats. Change or remove anything here.") })]
const form = h(
"form",
{
class: "mnew",
onsubmit: (e) => {
e.preventDefault()
add(e.currentTarget)
},
},
h("textarea", { class: "textarea", id: "memory-new", rows: "2", placeholder: t("Something to remember…"), "aria-label": t("Something to remember…") }),
h(
"div",
{ class: "mnew__foot" },
groups.length > 1 ? h("select", { "aria-label": t("Data group") }, groups.map((g) => h("option", { value: g.id, text: g.name }))) : h("span"),
h("button", { type: "submit", class: "btn btn--sm btn--primary", text: t("Add") }),
),
)
form.querySelector("textarea").addEventListener("keydown", (e) => {
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) form.requestSubmit()
})
parts.push(form)
if (error) parts.push(h("p", { class: "status is-error", role: "alert", text: error }))
if (loading && !rows.length) parts.push(h("p", { class: "muted page__loading", text: t("Loading…") }))
else if (!rows.length && !error) parts.push(h("p", { class: "muted arch__empty", text: t("Nothing remembered yet.") }))
if (rows.length) parts.push(h("ul", { class: "arch mlist", "aria-label": t("Memory") }, rows.map(row)))
box.append(h("div", { class: "arch__page" }, parts))
if (mark) box.querySelector(`.mrow[data-id="${CSS.escape(mark)}"]`)?.scrollIntoView({ block: "center" })
}
+70 -8
View File
@@ -1,12 +1,16 @@
// The command palette (Ctrl+K): one line to type into, and what matches -- the app's commands, // The command palette (Ctrl+K): one line to type into, and what matches -- the app's commands,
// and the chats of both kinds by title, newest first. Up and Down move, Enter does it, Escape // the chats of both kinds by title, newest first, and below them what the server's search finds
// closes. The chats are asked of the server each time it opens, so a chat started elsewhere // inside them, in the file library and in memory (`GET /search`, once two letters are typed and
// is there. // the typing pauses). Up and Down move, Enter does it, Escape closes. The chats are asked of the
// server each time it opens, so a chat started elsewhere is there.
//
// A hit's snippet is plain text with the ranges that matched (`marks`): drawn as text, the
// ranges as <mark> elements. Titles and snippets may be a model's words; nothing is markup.
import { h, clear, icon, ICONS, when } from "./dom.js" import { h, clear, icon, ICONS, when } from "./dom.js"
import { get } from "./bridge.js" import { get } from "./bridge.js"
let ctx = null // { commands(): [{ label, hint?, run }], open(id) } let ctx = null // { commands(): [{ label, hint?, run }], open(chat), openFile(path, name), openMemory(id) }
let dlg = null let dlg = null
/** Lower case, without diacritics: "Príliš" is found by "prilis". */ /** Lower case, without diacritics: "Príliš" is found by "prilis". */
@@ -17,6 +21,22 @@ function fold(text) {
.toLowerCase() .toLowerCase()
} }
/** `text` with the `[start, end)` ranges of `marks` as <mark> elements: text nodes only. */
function marked(text, marks) {
const el = h("span", { class: "palette__snippet" })
let at = 0
const chars = [...String(text || "")]
for (const [a, b] of (marks || []).filter((m) => Array.isArray(m) && m[1] > m[0]).sort((x, y) => x[0] - y[0])) {
if (a < at) continue
el.append(chars.slice(at, a).join(""), h("mark", { text: chars.slice(a, b).join("") }))
at = b
}
el.append(chars.slice(at).join(""))
return el
}
const SEARCH_KIND = { chats: () => t("Chats"), files: () => t("Files"), memories: () => t("Memory") }
function matches(text, words) { function matches(text, words) {
const f = fold(text) const f = fold(text)
return words.every((w) => f.includes(w)) return words.every((w) => f.includes(w))
@@ -31,7 +51,11 @@ export async function openPalette() {
let chats = [] let chats = []
let shown = [] let shown = []
let at = 0 let at = 0
let found = [] // the server's hits for `asked`
let asked = ""
let timer = 0
const close = () => { const close = () => {
clearTimeout(timer)
if (!dlg) return if (!dlg) return
dlg.close() dlg.close()
dlg.remove() dlg.remove()
@@ -44,16 +68,32 @@ export async function openPalette() {
const draw = () => { const draw = () => {
const words = fold(input.value).split(/\s+/).filter(Boolean) const words = fold(input.value).split(/\s+/).filter(Boolean)
const commands = ctx.commands().filter((c) => matches(c.label, words)) const commands = ctx.commands().filter((c) => matches(c.label, words))
const found = chats.filter((c) => matches(c.title || t("New chat"), words)).slice(0, 30) const titled = chats.filter((c) => matches(c.title || t("New chat"), words)).slice(0, 30)
shown = [ shown = [
...commands.map((c) => ({ kind: "command", label: c.label, hint: c.hint || "", run: c.run })), ...commands.map((c) => ({ kind: "command", label: c.label, hint: c.hint || "", run: c.run })),
...found.map((c) => ({ ...titled.map((c) => ({
kind: c.kind, kind: c.kind,
label: c.title || t("New chat"), label: c.title || t("New chat"),
hint: `${c.kind === "agent" ? t("Agent") : t("Chats")} · ${when(c.updated_at)}`, hint: `${c.kind === "agent" ? t("Agent") : t("Chats")} · ${when(c.updated_at)}`,
run: () => ctx.open(c), run: () => ctx.open(c),
})), })),
] ]
// What the server found inside: the chats already listed by their title once only.
const listed = new Set(titled.map((c) => c.id))
for (const group of input.value.trim() === asked ? found : []) {
for (const hit of group.hits || []) {
if (hit.kind === "chat" && listed.has(hit.id)) continue
const kind = { chat: "found-chat", file: "found-file", memory: "found-memory" }[hit.kind]
if (!kind) continue
shown.push({
kind,
label: hit.title || (hit.kind === "chat" ? t("New chat") : hit.kind === "file" ? String(hit.path || "").split("/").pop() : t("Memory")),
snippet: hit.snippet ? marked(hit.snippet, hit.marks) : null,
hint: SEARCH_KIND[group.kind]?.() || "",
run: () => (hit.kind === "chat" ? ctx.open({ id: hit.id, kind: hit.agent ? "agent" : "chat" }) : hit.kind === "file" ? ctx.openFile(hit.path, hit.title) : ctx.openMemory(hit.id)),
})
}
}
at = Math.min(at, Math.max(0, shown.length - 1)) at = Math.min(at, Math.max(0, shown.length - 1))
clear(list) clear(list)
if (!shown.length) list.append(h("li", { class: "palette__none", text: t("Nothing matches.") })) if (!shown.length) list.append(h("li", { class: "palette__none", text: t("Nothing matches.") }))
@@ -74,8 +114,10 @@ export async function openPalette() {
} }
}, },
}, },
h("span", { class: "palette__icon" }, icon(item.kind === "command" ? ICONS.chevron : item.kind === "agent" ? ICONS.folder : ICONS.file)), h("span", { class: "palette__icon" }, icon(item.kind === "command" ? ICONS.chevron : item.kind === "found-memory" ? ICONS.bulb : item.kind.startsWith("found") ? ICONS.search : item.kind === "agent" ? ICONS.folder : ICONS.file)),
h("span", { class: "palette__label", text: item.label }), item.snippet
? h("span", { class: "palette__label palette__label--two" }, h("span", { class: "palette__title", text: item.label }), item.snippet)
: h("span", { class: "palette__label", text: item.label }),
h("span", { class: "palette__hint", text: item.hint }), h("span", { class: "palette__hint", text: item.hint }),
), ),
) )
@@ -87,9 +129,29 @@ export async function openPalette() {
input.setAttribute("aria-activedescendant", shown.length ? `palette-${at}` : "") input.setAttribute("aria-activedescendant", shown.length ? `palette-${at}` : "")
list.querySelector(`#palette-${at}`)?.scrollIntoView({ block: "nearest" }) list.querySelector(`#palette-${at}`)?.scrollIntoView({ block: "nearest" })
} }
// The server's search, once the typing pauses: an older server without it finds nothing more.
const search = () => {
clearTimeout(timer)
const q = input.value.trim()
if (q.length < 2) {
found = []
asked = ""
return
}
timer = setTimeout(async () => {
try {
const r = await get(`/search?q=${encodeURIComponent(q)}&in=all&limit=8`)
if (!dlg || input.value.trim() !== q) return
found = r.results || []
asked = q
draw()
} catch {}
}, 250)
}
input.addEventListener("input", () => { input.addEventListener("input", () => {
at = 0 at = 0
draw() draw()
search()
}) })
input.addEventListener("keydown", (e) => { input.addEventListener("keydown", (e) => {
if (e.key === "ArrowDown" || e.key === "ArrowUp") { if (e.key === "ArrowDown" || e.key === "ArrowUp") {