Memory page and search inside chats, files and memory (Ctrl+K, GET /search)
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:
9 files changed
+515
-11
No files matched your search
@@ -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
|
||||
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.
|
||||
- **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
|
||||
|
||||
|
||||
@@ -484,6 +484,15 @@ KEYS = (
|
||||
"New folder…",
|
||||
"Create",
|
||||
"“%(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
|
||||
@@ -732,6 +741,13 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"New folder…": "Nová složka…",
|
||||
"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.",
|
||||
"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": {
|
||||
"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…",
|
||||
"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.",
|
||||
"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": {
|
||||
"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…",
|
||||
"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.",
|
||||
"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": {
|
||||
"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…",
|
||||
"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.",
|
||||
"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": {
|
||||
"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…",
|
||||
"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.",
|
||||
"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": {
|
||||
"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…",
|
||||
"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.",
|
||||
"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": {
|
||||
"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…",
|
||||
"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.",
|
||||
"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": {
|
||||
"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…",
|
||||
"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.",
|
||||
"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á.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1200,6 +1200,99 @@ test("F8: at 390 px the folders stay inside the sidebar", async () => {
|
||||
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 ----------------------------------------------------------------------------------------
|
||||
|
||||
const only = process.argv[2] || ""
|
||||
|
||||
+27
@@ -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__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.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); }
|
||||
|
||||
|
||||
@@ -1691,6 +1696,28 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
||||
.arch__page { display: grid; gap: var(--sp-3); }
|
||||
.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 ---------------------------------------------------------------------- */
|
||||
|
||||
/* 64rem (--bp-wide): a panel goes over the chat instead of beside it. */
|
||||
|
||||
+64
@@ -44,6 +44,7 @@ const LEMBAS_I18N = {
|
||||
"About": "O aplikaci",
|
||||
"Account": "Účet",
|
||||
"Activity": "Aktivita",
|
||||
"Add": "Přidat",
|
||||
"Add folder…": "Přidat složku…",
|
||||
"Address of your LLeMbas server": "Adresa vašeho serveru LLeMbas",
|
||||
"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é.",
|
||||
"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í.",
|
||||
"Data group": "Datová skupina",
|
||||
"Days": "Dny",
|
||||
"Delete": "Smazat",
|
||||
"Delete chat": "Smazat konverzaci",
|
||||
@@ -167,6 +169,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — nejdřív se zeptá",
|
||||
"Max": "Max",
|
||||
"Maximise": "Maximalizovat",
|
||||
"Memory": "Paměť",
|
||||
"Memory and personal": "Paměť a osobní",
|
||||
"Menu": "Nabídka",
|
||||
"Message": "Zpráva",
|
||||
@@ -201,10 +204,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Neměřeno.",
|
||||
"Not run yet": "Ještě neběžela",
|
||||
"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 said in the room yet.": "V místnosti zatím nikdo nic neřekl.",
|
||||
"Nothing is scheduled.": "Nic není naplánováno.",
|
||||
"Nothing matches.": "Nic neodpovídá.",
|
||||
"Nothing remembered yet.": "Zatím si nic nepamatuje.",
|
||||
"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 to report": "Nic k hlášení",
|
||||
@@ -245,6 +250,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Vygenerovat odpověď znovu",
|
||||
"Remove": "Odebrat",
|
||||
"Remove %(dir)s": "Odebrat %(dir)s",
|
||||
"Remove this memory?": "Odstranit tento záznam z paměti?",
|
||||
"Rename": "Přejmenovat",
|
||||
"Rename chat": "Přejmenovat konverzaci",
|
||||
"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.",
|
||||
"Since the last reply": "Od poslední odpovědi",
|
||||
"Skipped": "Vynecháno",
|
||||
"Something to remember…": "Něco k zapamatování…",
|
||||
"Source": "Zdroj",
|
||||
"Start the agent": "Spustit agenta",
|
||||
"Stop": "Zastavit",
|
||||
@@ -382,6 +389,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "Čeká se na {{model}}: {{reason}}",
|
||||
"What it does": "Co dělá",
|
||||
"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 it matters": "Když na tom záleží",
|
||||
"Writing": "Píše",
|
||||
@@ -476,6 +484,7 @@ const LEMBAS_I18N = {
|
||||
"About": "Über",
|
||||
"Account": "Konto",
|
||||
"Activity": "Aktivität",
|
||||
"Add": "Hinzufügen",
|
||||
"Add folder…": "Ordner hinzufügen…",
|
||||
"Address of your LLeMbas server": "Adresse Ihres LLeMbas-Servers",
|
||||
"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.",
|
||||
"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.",
|
||||
"Data group": "Datengruppe",
|
||||
"Days": "Tage",
|
||||
"Delete": "Löschen",
|
||||
"Delete chat": "Chat löschen",
|
||||
@@ -599,6 +609,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — fragt zuerst",
|
||||
"Max": "Max",
|
||||
"Maximise": "Maximieren",
|
||||
"Memory": "Gedächtnis",
|
||||
"Memory and personal": "Gedächtnis und Persönliches",
|
||||
"Menu": "Menü",
|
||||
"Message": "Nachricht",
|
||||
@@ -633,10 +644,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Nicht gemessen.",
|
||||
"Not run yet": "Noch nicht gelaufen",
|
||||
"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 said in the room yet.": "Im Raum wurde noch nichts gesagt.",
|
||||
"Nothing is scheduled.": "Nichts ist geplant.",
|
||||
"Nothing matches.": "Nichts gefunden.",
|
||||
"Nothing remembered yet.": "Noch nichts behalten.",
|
||||
"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 to report": "Nichts zu melden",
|
||||
@@ -677,6 +690,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Antwort neu generieren",
|
||||
"Remove": "Entfernen",
|
||||
"Remove %(dir)s": "%(dir)s entfernen",
|
||||
"Remove this memory?": "Diese Erinnerung entfernen?",
|
||||
"Rename": "Umbenennen",
|
||||
"Rename chat": "Chat 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.",
|
||||
"Since the last reply": "Seit der letzten Antwort",
|
||||
"Skipped": "Ausgelassen",
|
||||
"Something to remember…": "Etwas, das behalten werden soll…",
|
||||
"Source": "Quelltext",
|
||||
"Start the agent": "Agent starten",
|
||||
"Stop": "Stoppen",
|
||||
@@ -814,6 +829,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "Warte auf {{model}}: {{reason}}",
|
||||
"What it does": "Was sie tut",
|
||||
"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 it matters": "Wenn es zählt",
|
||||
"Writing": "Schreibe",
|
||||
@@ -908,6 +924,7 @@ const LEMBAS_I18N = {
|
||||
"About": "Acerca de",
|
||||
"Account": "Cuenta",
|
||||
"Activity": "Actividad",
|
||||
"Add": "Añadir",
|
||||
"Add folder…": "Añadir carpeta…",
|
||||
"Address of your LLeMbas server": "Dirección de su servidor LLeMbas",
|
||||
"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.",
|
||||
"Create": "Crear",
|
||||
"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",
|
||||
"Delete": "Eliminar",
|
||||
"Delete chat": "Eliminar chat",
|
||||
@@ -1031,6 +1049,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — pregunta primero",
|
||||
"Max": "Máx",
|
||||
"Maximise": "Maximizar",
|
||||
"Memory": "Memoria",
|
||||
"Memory and personal": "Memoria y datos personales",
|
||||
"Menu": "Menú",
|
||||
"Message": "Mensaje",
|
||||
@@ -1065,10 +1084,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Sin medir.",
|
||||
"Not run yet": "Aún no se ha ejecutado",
|
||||
"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 said in the room yet.": "Todavía no se ha dicho nada en la sala.",
|
||||
"Nothing is scheduled.": "No hay nada programado.",
|
||||
"Nothing matches.": "No hay coincidencias.",
|
||||
"Nothing remembered yet.": "Todavía no se recuerda nada.",
|
||||
"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 to report": "Nada que informar",
|
||||
@@ -1109,6 +1130,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Regenerar respuesta",
|
||||
"Remove": "Quitar",
|
||||
"Remove %(dir)s": "Quitar %(dir)s",
|
||||
"Remove this memory?": "¿Quitar este recuerdo?",
|
||||
"Rename": "Renombrar",
|
||||
"Rename chat": "Renombrar chat",
|
||||
"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.",
|
||||
"Since the last reply": "Desde la última respuesta",
|
||||
"Skipped": "Omitida",
|
||||
"Something to remember…": "Algo que recordar…",
|
||||
"Source": "Código fuente",
|
||||
"Start the agent": "Iniciar el agente",
|
||||
"Stop": "Detener",
|
||||
@@ -1246,6 +1269,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "Esperando a {{model}}: {{reason}}",
|
||||
"What it does": "Qué hace",
|
||||
"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 it matters": "Cuando importa",
|
||||
"Writing": "Escribiendo",
|
||||
@@ -1340,6 +1364,7 @@ const LEMBAS_I18N = {
|
||||
"About": "À propos",
|
||||
"Account": "Compte",
|
||||
"Activity": "Activité",
|
||||
"Add": "Ajouter",
|
||||
"Add folder…": "Ajouter un dossier…",
|
||||
"Address of your LLeMbas server": "Adresse de votre serveur LLeMbas",
|
||||
"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.",
|
||||
"Create": "Créer",
|
||||
"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",
|
||||
"Delete": "Supprimer",
|
||||
"Delete chat": "Supprimer la conversation",
|
||||
@@ -1463,6 +1489,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — demande d'abord",
|
||||
"Max": "Max",
|
||||
"Maximise": "Agrandir",
|
||||
"Memory": "Mémoire",
|
||||
"Memory and personal": "Mémoire et informations personnelles",
|
||||
"Menu": "Menu",
|
||||
"Message": "Message",
|
||||
@@ -1497,10 +1524,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Non mesuré.",
|
||||
"Not run yet": "Pas encore exécutée",
|
||||
"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 said in the room yet.": "Rien n’a encore été dit dans la salle.",
|
||||
"Nothing is scheduled.": "Rien n’est planifié.",
|
||||
"Nothing matches.": "Aucun résultat.",
|
||||
"Nothing remembered yet.": "Rien n’est encore retenu.",
|
||||
"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 to report": "Rien à signaler",
|
||||
@@ -1541,6 +1570,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Régénérer la réponse",
|
||||
"Remove": "Retirer",
|
||||
"Remove %(dir)s": "Retirer %(dir)s",
|
||||
"Remove this memory?": "Supprimer ce souvenir ?",
|
||||
"Rename": "Renommer",
|
||||
"Rename chat": "Renommer la conversation",
|
||||
"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.",
|
||||
"Since the last reply": "Depuis la dernière réponse",
|
||||
"Skipped": "Ignorée",
|
||||
"Something to remember…": "Quelque chose à retenir…",
|
||||
"Source": "Source",
|
||||
"Start the agent": "Démarrer l'agent",
|
||||
"Stop": "Arrêter",
|
||||
@@ -1678,6 +1709,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "En attente de {{model}} : {{reason}}",
|
||||
"What it does": "Ce qu’elle fait",
|
||||
"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 it matters": "Quand c'est important",
|
||||
"Writing": "Écriture",
|
||||
@@ -1772,6 +1804,7 @@ const LEMBAS_I18N = {
|
||||
"About": "Névjegy",
|
||||
"Account": "Fiók",
|
||||
"Activity": "Tevékenység",
|
||||
"Add": "Hozzáadás",
|
||||
"Add folder…": "Mappa hozzáadása…",
|
||||
"Address of your LLeMbas server": "A LLeMbas-kiszolgáló címe",
|
||||
"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.",
|
||||
"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.",
|
||||
"Data group": "Adatcsoport",
|
||||
"Days": "Napok",
|
||||
"Delete": "Törlés",
|
||||
"Delete chat": "Csevegés törlése",
|
||||
@@ -1895,6 +1929,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — előbb kérdez",
|
||||
"Max": "Max",
|
||||
"Maximise": "Teljes méret",
|
||||
"Memory": "Memória",
|
||||
"Memory and personal": "Memória és személyes adatok",
|
||||
"Menu": "Menü",
|
||||
"Message": "Üzenet",
|
||||
@@ -1929,10 +1964,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Nincs mérve.",
|
||||
"Not run yet": "Még nem futott",
|
||||
"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 said in the room yet.": "A szobában még senki nem mondott semmit.",
|
||||
"Nothing is scheduled.": "Nincs semmi ütemezve.",
|
||||
"Nothing matches.": "Nincs találat.",
|
||||
"Nothing remembered yet.": "Még nincs megjegyezve semmi.",
|
||||
"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 to report": "Nincs jelentenivaló",
|
||||
@@ -1973,6 +2010,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Válasz újragenerálása",
|
||||
"Remove": "Eltávolítás",
|
||||
"Remove %(dir)s": "%(dir)s eltávolítása",
|
||||
"Remove this memory?": "Eltávolítja ezt az emléket?",
|
||||
"Rename": "Átnevezés",
|
||||
"Rename chat": "Csevegés á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.",
|
||||
"Since the last reply": "Az utolsó válasz óta",
|
||||
"Skipped": "Kihagyva",
|
||||
"Something to remember…": "Valami, amit meg kell jegyezni…",
|
||||
"Source": "Forrás",
|
||||
"Start the agent": "Ügynök indítása",
|
||||
"Stop": "Leállítás",
|
||||
@@ -2110,6 +2149,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "Várakozás a(z) {{model}} modellre: {{reason}}",
|
||||
"What it does": "Mit csinál",
|
||||
"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 it matters": "Ha számít",
|
||||
"Writing": "Írás",
|
||||
@@ -2204,6 +2244,7 @@ const LEMBAS_I18N = {
|
||||
"About": "Informazioni",
|
||||
"Account": "Account",
|
||||
"Activity": "Attività",
|
||||
"Add": "Aggiungi",
|
||||
"Add folder…": "Aggiungi cartella…",
|
||||
"Address of your LLeMbas server": "Indirizzo del tuo server LLeMbas",
|
||||
"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.",
|
||||
"Create": "Crea",
|
||||
"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",
|
||||
"Delete": "Elimina",
|
||||
"Delete chat": "Elimina chat",
|
||||
@@ -2327,6 +2369,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — chiede prima",
|
||||
"Max": "Max",
|
||||
"Maximise": "Ingrandisci",
|
||||
"Memory": "Memoria",
|
||||
"Memory and personal": "Memoria e dati personali",
|
||||
"Menu": "Menu",
|
||||
"Message": "Messaggio",
|
||||
@@ -2361,10 +2404,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Non misurato.",
|
||||
"Not run yet": "Non ancora eseguita",
|
||||
"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 said in the room yet.": "Nella stanza non è ancora stato detto nulla.",
|
||||
"Nothing is scheduled.": "Non c’è niente di pianificato.",
|
||||
"Nothing matches.": "Nessun risultato.",
|
||||
"Nothing remembered yet.": "Ancora niente da ricordare.",
|
||||
"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 to report": "Niente da segnalare",
|
||||
@@ -2405,6 +2450,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Rigenera risposta",
|
||||
"Remove": "Rimuovi",
|
||||
"Remove %(dir)s": "Rimuovi %(dir)s",
|
||||
"Remove this memory?": "Rimuovere questo ricordo?",
|
||||
"Rename": "Rinomina",
|
||||
"Rename chat": "Rinomina chat",
|
||||
"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.",
|
||||
"Since the last reply": "Dall’ultima risposta",
|
||||
"Skipped": "Saltata",
|
||||
"Something to remember…": "Qualcosa da ricordare…",
|
||||
"Source": "Sorgente",
|
||||
"Start the agent": "Avvia l'agente",
|
||||
"Stop": "Ferma",
|
||||
@@ -2542,6 +2589,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "In attesa di {{model}}: {{reason}}",
|
||||
"What it does": "Cosa fa",
|
||||
"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 it matters": "Quando conta",
|
||||
"Writing": "Scrittura",
|
||||
@@ -2636,6 +2684,7 @@ const LEMBAS_I18N = {
|
||||
"About": "O aplikacji",
|
||||
"Account": "Konto",
|
||||
"Activity": "Aktywność",
|
||||
"Add": "Dodaj",
|
||||
"Add folder…": "Dodaj folder…",
|
||||
"Address of your LLeMbas server": "Adres Twojego serwera LLeMbas",
|
||||
"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.",
|
||||
"Create": "Utwórz",
|
||||
"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",
|
||||
"Delete": "Usuń",
|
||||
"Delete chat": "Usuń czat",
|
||||
@@ -2759,6 +2809,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — najpierw pyta",
|
||||
"Max": "Max",
|
||||
"Maximise": "Maksymalizuj",
|
||||
"Memory": "Pamięć",
|
||||
"Memory and personal": "Pamięć i dane osobiste",
|
||||
"Menu": "Menu",
|
||||
"Message": "Wiadomość",
|
||||
@@ -2793,10 +2844,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Nie zmierzono.",
|
||||
"Not run yet": "Jeszcze nie uruchomione",
|
||||
"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 said in the room yet.": "W pokoju nikt jeszcze nic nie powiedział.",
|
||||
"Nothing is scheduled.": "Nic nie jest zaplanowane.",
|
||||
"Nothing matches.": "Nic nie pasuje.",
|
||||
"Nothing remembered yet.": "Na razie nic nie zapamiętano.",
|
||||
"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 to report": "Nic do zgłoszenia",
|
||||
@@ -2837,6 +2890,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Wygeneruj odpowiedź ponownie",
|
||||
"Remove": "Usuń",
|
||||
"Remove %(dir)s": "Usuń %(dir)s",
|
||||
"Remove this memory?": "Usunąć to wspomnienie?",
|
||||
"Rename": "Zmień nazwę",
|
||||
"Rename chat": "Zmień nazwę czatu",
|
||||
"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.",
|
||||
"Since the last reply": "Od ostatniej odpowiedzi",
|
||||
"Skipped": "Pominięty",
|
||||
"Something to remember…": "Coś do zapamiętania…",
|
||||
"Source": "Źródło",
|
||||
"Start the agent": "Uruchom agenta",
|
||||
"Stop": "Zatrzymać",
|
||||
@@ -2974,6 +3029,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "Czekam na {{model}}: {{reason}}",
|
||||
"What it does": "Co robi",
|
||||
"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 it matters": "Gdy to ważne",
|
||||
"Writing": "Pisanie",
|
||||
@@ -3068,6 +3124,7 @@ const LEMBAS_I18N = {
|
||||
"About": "O aplikácii",
|
||||
"Account": "Účet",
|
||||
"Activity": "Aktivita",
|
||||
"Add": "Pridať",
|
||||
"Add folder…": "Pridať priečinok…",
|
||||
"Address of your LLeMbas server": "Adresa vášho servera LLeMbas",
|
||||
"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é.",
|
||||
"Create": "Vytvoriť",
|
||||
"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",
|
||||
"Delete": "Zmazať",
|
||||
"Delete chat": "Zmazať konverzáciu",
|
||||
@@ -3191,6 +3249,7 @@ const LEMBAS_I18N = {
|
||||
"Manual — asks first": "Manual — najprv sa opýta",
|
||||
"Max": "Max",
|
||||
"Maximise": "Maximalizovať",
|
||||
"Memory": "Pamäť",
|
||||
"Memory and personal": "Pamäť a osobné",
|
||||
"Menu": "Ponuka",
|
||||
"Message": "Správa",
|
||||
@@ -3225,10 +3284,12 @@ const LEMBAS_I18N = {
|
||||
"Not measured.": "Nezmerané.",
|
||||
"Not run yet": "Ešte nebežala",
|
||||
"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 said in the room yet.": "V miestnosti zatiaľ nikto nič nepovedal.",
|
||||
"Nothing is scheduled.": "Nič nie je naplánované.",
|
||||
"Nothing matches.": "Nič nezodpovedá.",
|
||||
"Nothing remembered yet.": "Zatiaľ si nič nepamätá.",
|
||||
"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 to report": "Nič na hlásenie",
|
||||
@@ -3269,6 +3330,7 @@ const LEMBAS_I18N = {
|
||||
"Regenerate reply": "Vygenerovať odpoveď znova",
|
||||
"Remove": "Odstrániť",
|
||||
"Remove %(dir)s": "Odstrániť %(dir)s",
|
||||
"Remove this memory?": "Odstrániť tento záznam z pamäte?",
|
||||
"Rename": "Premenovať",
|
||||
"Rename chat": "Premenovať konverzáciu",
|
||||
"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.",
|
||||
"Since the last reply": "Od poslednej odpovede",
|
||||
"Skipped": "Vynechané",
|
||||
"Something to remember…": "Niečo na zapamätanie…",
|
||||
"Source": "Zdroj",
|
||||
"Start the agent": "Spustiť agenta",
|
||||
"Stop": "Zastaviť",
|
||||
@@ -3406,6 +3469,7 @@ const LEMBAS_I18N = {
|
||||
"Waiting for {{model}}: {{reason}}": "Čaká sa na {{model}}: {{reason}}",
|
||||
"What it does": "Čo robí",
|
||||
"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 it matters": "Keď na tom záleží",
|
||||
"Writing": "Píše",
|
||||
|
||||
@@ -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-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-memory" data-view="memory"><span class="navrow__icon"></span><span data-i18n>Memory</span></button>
|
||||
</div>
|
||||
<ul class="list" id="chat-list" aria-label="Chats" data-i18n-aria-label></ul>
|
||||
<div class="side__foot">
|
||||
@@ -133,6 +134,15 @@
|
||||
<div class="page__body" id="archived-body"></div>
|
||||
</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">
|
||||
<header class="head">
|
||||
<h1 class="head__title" id="settings-title" data-i18n>Settings</h1>
|
||||
|
||||
+20
-3
@@ -10,7 +10,7 @@ import { renderBar, initBar } from "./titlebar.js"
|
||||
import { renderSetup, initSetup } from "./setup.js"
|
||||
import { renderSettings, initSettings, scrollToSection } from "./settings.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 { initChat, openChat, newChat, closeChat, openChatId, chatMenu, statusChanged, draftKind, onThisComputer, modelsChanged, intoComposer } from "./chat.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 { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js"
|
||||
import { initArchived, showArchived } from "./archived.js"
|
||||
import { showMemory } from "./memory.js"
|
||||
import { closePopover } from "./popup.js"
|
||||
import { noteSubagentsCap } from "./models.js"
|
||||
|
||||
let status = null
|
||||
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 models = []
|
||||
let devices = [] // this account's linked devices
|
||||
@@ -156,6 +157,7 @@ function showPage(name) {
|
||||
$("page-files").hidden = name !== "files"
|
||||
$("page-scheduled").hidden = name !== "scheduled"
|
||||
$("page-archived").hidden = name !== "archived"
|
||||
$("page-memory").hidden = name !== "memory"
|
||||
$("settings").hidden = name !== "settings"
|
||||
for (const b of document.querySelectorAll(".navrow[data-view]")) b.setAttribute("aria-current", String(b.dataset.view === name))
|
||||
if (name !== "chat") {
|
||||
@@ -208,6 +210,11 @@ function showArchivedPage() {
|
||||
showArchived()
|
||||
}
|
||||
|
||||
function showMemoryPage(focus) {
|
||||
showPage("memory")
|
||||
showMemory(focus || "")
|
||||
}
|
||||
|
||||
function showFiles(which) {
|
||||
showPage("files")
|
||||
if (which) showFilesTab(which)
|
||||
@@ -223,6 +230,7 @@ function commands() {
|
||||
...scheduledCommands(),
|
||||
{ label: t("Files"), run: () => showFiles() },
|
||||
{ label: t("Archived"), run: showArchivedPage },
|
||||
{ label: t("Memory"), run: () => showMemoryPage() },
|
||||
{ label: t("Settings"), run: () => showSettings(true) },
|
||||
]
|
||||
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.
|
||||
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") })
|
||||
onViewer((on) => {
|
||||
$("shell").dataset.viewer = String(on)
|
||||
@@ -340,6 +353,10 @@ async function boot() {
|
||||
$("nav-archived").addEventListener("click", showArchivedPage)
|
||||
$("archived-refresh").replaceChildren(icon(ICONS.refresh))
|
||||
$("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()
|
||||
$("settings-btn").addEventListener("click", () => showSettings(page !== "settings"))
|
||||
$("settings-close").replaceChildren(icon(ICONS.close))
|
||||
|
||||
+159
@@ -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
@@ -1,12 +1,16 @@
|
||||
// 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
|
||||
// closes. The chats are asked of the server each time it opens, so a chat started elsewhere
|
||||
// is there.
|
||||
// the chats of both kinds by title, newest first, and below them what the server's search finds
|
||||
// inside them, in the file library and in memory (`GET /search`, once two letters are typed and
|
||||
// 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 { 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
|
||||
|
||||
/** Lower case, without diacritics: "Príliš" is found by "prilis". */
|
||||
@@ -17,6 +21,22 @@ function fold(text) {
|
||||
.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) {
|
||||
const f = fold(text)
|
||||
return words.every((w) => f.includes(w))
|
||||
@@ -31,7 +51,11 @@ export async function openPalette() {
|
||||
let chats = []
|
||||
let shown = []
|
||||
let at = 0
|
||||
let found = [] // the server's hits for `asked`
|
||||
let asked = ""
|
||||
let timer = 0
|
||||
const close = () => {
|
||||
clearTimeout(timer)
|
||||
if (!dlg) return
|
||||
dlg.close()
|
||||
dlg.remove()
|
||||
@@ -44,16 +68,32 @@ export async function openPalette() {
|
||||
const draw = () => {
|
||||
const words = fold(input.value).split(/\s+/).filter(Boolean)
|
||||
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 = [
|
||||
...commands.map((c) => ({ kind: "command", label: c.label, hint: c.hint || "", run: c.run })),
|
||||
...found.map((c) => ({
|
||||
...titled.map((c) => ({
|
||||
kind: c.kind,
|
||||
label: c.title || t("New chat"),
|
||||
hint: `${c.kind === "agent" ? t("Agent") : t("Chats")} · ${when(c.updated_at)}`,
|
||||
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))
|
||||
clear(list)
|
||||
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__label", text: item.label }),
|
||||
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)),
|
||||
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 }),
|
||||
),
|
||||
)
|
||||
@@ -87,9 +129,29 @@ export async function openPalette() {
|
||||
input.setAttribute("aria-activedescendant", shown.length ? `palette-${at}` : "")
|
||||
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", () => {
|
||||
at = 0
|
||||
draw()
|
||||
search()
|
||||
})
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
|
||||
Reference in new issue
Block a user