Files in the web explorer's shape: crumbs, folder tree, list in columns, details and preview
Both tabs use the window's width now, as the web's explorer does: where you are (crumbs), the folder tree (a chevron opens, a name lists), the list on one grid with every row a subgrid -- name with an icon by kind, type, size, changed, a menu -- and on a wide window the chosen file's details, the start of it and Open in the viewer (a double click opens it). The panes give way by the room the page has (container queries, so the sidebar's share counts): the details at 60rem, the tree at 40rem, the type and date columns when the list is under 28rem; a file then opens in the viewer on a click. The tree and the list share one request per folder; every reading keeps the never-empty states of the previous commit. Tests (tests/ui FX): the administrator's / wide (tree, columns on one track, details, preview, viewer), navigating by list, tree and crumbs with a long name, this computer's folder, and every width 1920/1280/1024/800/390 with nothing sideways, every control inside the window and the name's track never closed. H8/N8 follow the new list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
9 files changed
+987
-227
No files matched your search
@@ -6,6 +6,17 @@ All notable changes to LLeMbas Desktop. The format follows
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Files, in the web explorer's shape, using the window's width.** Both tabs (This computer and
|
||||||
|
Server) are now where you are (crumbs, each part a way back), the folder tree, the list in
|
||||||
|
columns -- name with an icon by kind, type, size, changed -- and, on a wide window, the chosen
|
||||||
|
file's details with the start of it and Open in the viewer (a double click opens it too). With
|
||||||
|
less room the details give way first (a file then opens in the viewer), then the tree, then the
|
||||||
|
type and date columns, by the room the page has rather than the window's width; nothing is cut
|
||||||
|
off or scrolls the page sideways (checked at 1920, 1280, 1024, 800 and 390 px with a long name).
|
||||||
|
The tree and the list share one request per folder.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- **Files › Server showed an empty pane while it waited.** Nothing was drawn until the server's
|
- **Files › Server showed an empty pane while it waited.** Nothing was drawn until the server's
|
||||||
|
|||||||
@@ -546,6 +546,24 @@ KEYS = (
|
|||||||
# explorer.js: a directory still being read, or an answer that is not a listing
|
# explorer.js: a directory still being read, or an answer that is not a listing
|
||||||
"The server has not answered in %(n)s seconds.",
|
"The server has not answered in %(n)s seconds.",
|
||||||
"The server's answer could not be read.",
|
"The server's answer could not be read.",
|
||||||
|
# explorer.js: the Files page as the web's explorer (the server's words, but the last)
|
||||||
|
"Name",
|
||||||
|
"Type",
|
||||||
|
"Size",
|
||||||
|
"Changed",
|
||||||
|
"folder",
|
||||||
|
"mount",
|
||||||
|
"All files",
|
||||||
|
"Home",
|
||||||
|
"Folders",
|
||||||
|
"Where you are",
|
||||||
|
"Nothing here yet. Not even a hobbit hole.",
|
||||||
|
"Contents",
|
||||||
|
"Actions",
|
||||||
|
"More for %(name)s",
|
||||||
|
"Path",
|
||||||
|
"Choose a file to see it here.",
|
||||||
|
"Details",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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
|
||||||
@@ -828,6 +846,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "Odpovědí na další zprávu bude obrázek nakreslený podle ní.",
|
"The reply to the next message is a picture drawn from it.": "Odpovědí na další zprávu bude obrázek nakreslený podle ní.",
|
||||||
"The server has not answered in %(n)s seconds.": "Server neodpověděl do %(n)s sekund.",
|
"The server has not answered in %(n)s seconds.": "Server neodpověděl do %(n)s sekund.",
|
||||||
"The server's answer could not be read.": "Odpověď serveru nešlo přečíst.",
|
"The server's answer could not be read.": "Odpověď serveru nešlo přečíst.",
|
||||||
|
"Choose a file to see it here.": "Vyberte soubor a uvidíte ho tady.",
|
||||||
|
"Details": "Podrobnosti",
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
@@ -1105,6 +1125,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "Die Antwort auf die nächste Nachricht ist ein danach gezeichnetes Bild.",
|
"The reply to the next message is a picture drawn from it.": "Die Antwort auf die nächste Nachricht ist ein danach gezeichnetes Bild.",
|
||||||
"The server has not answered in %(n)s seconds.": "Der Server hat seit %(n)s Sekunden nicht geantwortet.",
|
"The server has not answered in %(n)s seconds.": "Der Server hat seit %(n)s Sekunden nicht geantwortet.",
|
||||||
"The server's answer could not be read.": "Die Antwort des Servers konnte nicht gelesen werden.",
|
"The server's answer could not be read.": "Die Antwort des Servers konnte nicht gelesen werden.",
|
||||||
|
"Choose a file to see it here.": "Wählen Sie eine Datei, um sie hier zu sehen.",
|
||||||
|
"Details": "Details",
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
@@ -1382,6 +1404,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "La respuesta al siguiente mensaje es una imagen dibujada a partir de él.",
|
"The reply to the next message is a picture drawn from it.": "La respuesta al siguiente mensaje es una imagen dibujada a partir de él.",
|
||||||
"The server has not answered in %(n)s seconds.": "El servidor no ha respondido en %(n)s segundos.",
|
"The server has not answered in %(n)s seconds.": "El servidor no ha respondido en %(n)s segundos.",
|
||||||
"The server's answer could not be read.": "No se pudo leer la respuesta del servidor.",
|
"The server's answer could not be read.": "No se pudo leer la respuesta del servidor.",
|
||||||
|
"Choose a file to see it here.": "Elige un archivo para verlo aquí.",
|
||||||
|
"Details": "Detalles",
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
@@ -1659,6 +1683,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "La réponse au prochain message est une image dessinée d’après lui.",
|
"The reply to the next message is a picture drawn from it.": "La réponse au prochain message est une image dessinée d’après lui.",
|
||||||
"The server has not answered in %(n)s seconds.": "Le serveur n’a pas répondu depuis %(n)s secondes.",
|
"The server has not answered in %(n)s seconds.": "Le serveur n’a pas répondu depuis %(n)s secondes.",
|
||||||
"The server's answer could not be read.": "La réponse du serveur n’a pas pu être lue.",
|
"The server's answer could not be read.": "La réponse du serveur n’a pas pu être lue.",
|
||||||
|
"Choose a file to see it here.": "Choisissez un fichier pour le voir ici.",
|
||||||
|
"Details": "Détails",
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
@@ -1936,6 +1962,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "A következő üzenetre adott válasz egy abból rajzolt kép lesz.",
|
"The reply to the next message is a picture drawn from it.": "A következő üzenetre adott válasz egy abból rajzolt kép lesz.",
|
||||||
"The server has not answered in %(n)s seconds.": "A szerver %(n)s másodperce nem válaszolt.",
|
"The server has not answered in %(n)s seconds.": "A szerver %(n)s másodperce nem válaszolt.",
|
||||||
"The server's answer could not be read.": "A szerver válasza nem olvasható.",
|
"The server's answer could not be read.": "A szerver válasza nem olvasható.",
|
||||||
|
"Choose a file to see it here.": "Válasszon egy fájlt, hogy itt lássa.",
|
||||||
|
"Details": "Részletek",
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
@@ -2213,6 +2241,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "La risposta al prossimo messaggio è un’immagine disegnata a partire da esso.",
|
"The reply to the next message is a picture drawn from it.": "La risposta al prossimo messaggio è un’immagine disegnata a partire da esso.",
|
||||||
"The server has not answered in %(n)s seconds.": "Il server non ha risposto in %(n)s secondi.",
|
"The server has not answered in %(n)s seconds.": "Il server non ha risposto in %(n)s secondi.",
|
||||||
"The server's answer could not be read.": "Non è stato possibile leggere la risposta del server.",
|
"The server's answer could not be read.": "Non è stato possibile leggere la risposta del server.",
|
||||||
|
"Choose a file to see it here.": "Scegli un file per vederlo qui.",
|
||||||
|
"Details": "Dettagli",
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
@@ -2490,6 +2520,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "Odpowiedzią na następną wiadomość będzie obraz narysowany na jej podstawie.",
|
"The reply to the next message is a picture drawn from it.": "Odpowiedzią na następną wiadomość będzie obraz narysowany na jej podstawie.",
|
||||||
"The server has not answered in %(n)s seconds.": "Serwer nie odpowiedział od %(n)s sekund.",
|
"The server has not answered in %(n)s seconds.": "Serwer nie odpowiedział od %(n)s sekund.",
|
||||||
"The server's answer could not be read.": "Nie udało się odczytać odpowiedzi serwera.",
|
"The server's answer could not be read.": "Nie udało się odczytać odpowiedzi serwera.",
|
||||||
|
"Choose a file to see it here.": "Wybierz plik, aby zobaczyć go tutaj.",
|
||||||
|
"Details": "Szczegóły",
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
@@ -2767,6 +2799,8 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"The reply to the next message is a picture drawn from it.": "Odpoveďou na ďalšiu správu bude obrázok nakreslený podľa nej.",
|
"The reply to the next message is a picture drawn from it.": "Odpoveďou na ďalšiu správu bude obrázok nakreslený podľa nej.",
|
||||||
"The server has not answered in %(n)s seconds.": "Server neodpovedal do %(n)s sekúnd.",
|
"The server has not answered in %(n)s seconds.": "Server neodpovedal do %(n)s sekúnd.",
|
||||||
"The server's answer could not be read.": "Odpoveď servera sa nedala prečítať.",
|
"The server's answer could not be read.": "Odpoveď servera sa nedala prečítať.",
|
||||||
|
"Choose a file to see it here.": "Vyberte súbor a uvidíte ho tu.",
|
||||||
|
"Details": "Podrobnosti",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+152
-33
@@ -1320,7 +1320,7 @@ function h8(over = {}, commands = {}) {
|
|||||||
async function serverPane(p) {
|
async function serverPane(p) {
|
||||||
await p.click("#nav-files")
|
await p.click("#nav-files")
|
||||||
await p.click('#files-tabs [data-tab="server"]')
|
await p.click('#files-tabs [data-tab="server"]')
|
||||||
await p.waitForSelector('#server-tree li[data-path="/home/frodo/lembas.md"]')
|
await p.waitForSelector('#server-list .frow[data-path="/home/frodo/lembas.md"]')
|
||||||
}
|
}
|
||||||
|
|
||||||
test("H8: the server's files: upload, a new folder, rename and delete, each read again from the server", async () => {
|
test("H8: the server's files: upload, a new folder, rename and delete, each read again from the server", async () => {
|
||||||
@@ -1332,21 +1332,21 @@ test("H8: the server's files: upload, a new folder, rename and delete, each read
|
|||||||
assert.equal((await p.evaluate(() => window.__mock.calls.find((c) => c.cmd === "library_upload").args.dir)), "/home/frodo")
|
assert.equal((await p.evaluate(() => window.__mock.calls.find((c) => c.cmd === "library_upload").args.dir)), "/home/frodo")
|
||||||
await p.waitForFunction(() => window.__mock.api("GET", "/files?path=%2F").length === 2)
|
await p.waitForFunction(() => window.__mock.api("GET", "/files?path=%2F").length === 2)
|
||||||
// A new folder inside a folder, from its row's menu.
|
// A new folder inside a folder, from its row's menu.
|
||||||
await p.click('#server-tree li[data-path="/home/frodo/recipes"] > .tree__more')
|
await p.click('#server-list .frow[data-path="/home/frodo/recipes"] .frow__more')
|
||||||
await p.click(".menu .menu__item >> text=New folder")
|
await p.click(".menu .menu__item >> text=New folder")
|
||||||
await p.fill("dialog[open] .dialog__input", "Breads")
|
await p.fill("dialog[open] .dialog__input", "Breads")
|
||||||
await p.click("dialog[open] .btn--primary")
|
await p.click("dialog[open] .btn--primary")
|
||||||
await p.waitForFunction(() => window.__mock.api("POST", "/files/mkdir").length === 1)
|
await p.waitForFunction(() => window.__mock.api("POST", "/files/mkdir").length === 1)
|
||||||
assert.deepEqual((await p.evaluate(() => window.__mock.api("POST", "/files/mkdir")))[0].args.body, { path: "/home/frodo/recipes", name: "Breads" })
|
assert.deepEqual((await p.evaluate(() => window.__mock.api("POST", "/files/mkdir")))[0].args.body, { path: "/home/frodo/recipes", name: "Breads" })
|
||||||
// Rename a file.
|
// Rename a file.
|
||||||
await p.click('#server-tree li[data-path="/home/frodo/lembas.md"] > .tree__more')
|
await p.click('#server-list .frow[data-path="/home/frodo/lembas.md"] .frow__more')
|
||||||
await p.click(".menu .menu__item >> text=Rename")
|
await p.click(".menu .menu__item >> text=Rename")
|
||||||
await p.fill("dialog[open] .dialog__input", "waybread.md")
|
await p.fill("dialog[open] .dialog__input", "waybread.md")
|
||||||
await p.click("dialog[open] .btn--primary")
|
await p.click("dialog[open] .btn--primary")
|
||||||
await p.waitForFunction(() => window.__mock.api("POST", "/files/rename").length === 1)
|
await p.waitForFunction(() => window.__mock.api("POST", "/files/rename").length === 1)
|
||||||
assert.deepEqual((await p.evaluate(() => window.__mock.api("POST", "/files/rename")))[0].args.body, { path: "/home/frodo/lembas.md", name: "waybread.md" })
|
assert.deepEqual((await p.evaluate(() => window.__mock.api("POST", "/files/rename")))[0].args.body, { path: "/home/frodo/lembas.md", name: "waybread.md" })
|
||||||
// Delete: to the trash, after asking.
|
// Delete: to the trash, after asking.
|
||||||
await p.click('#server-tree li[data-path="/home/frodo/lembas.md"] > .tree__more')
|
await p.click('#server-list .frow[data-path="/home/frodo/lembas.md"] .frow__more')
|
||||||
await p.click(".menu .menu__item >> text=Delete")
|
await p.click(".menu .menu__item >> text=Delete")
|
||||||
await p.click("dialog[open] .btn--danger")
|
await p.click("dialog[open] .btn--danger")
|
||||||
await p.waitForFunction(() => window.__mock.api("DELETE", "/files?path=%2Fhome%2Ffrodo%2Flembas.md").length === 1)
|
await p.waitForFunction(() => window.__mock.api("DELETE", "/files?path=%2Fhome%2Ffrodo%2Flembas.md").length === 1)
|
||||||
@@ -1362,7 +1362,7 @@ test("H8: a refusal or a file the server did not take is said in the pane", asyn
|
|||||||
await p.click("#server-upload")
|
await p.click("#server-upload")
|
||||||
await p.waitForSelector("#server-status.is-error")
|
await p.waitForSelector("#server-status.is-error")
|
||||||
assert.equal(await p.textContent("#server-status"), "big.iso: This file is too large to send.")
|
assert.equal(await p.textContent("#server-status"), "big.iso: This file is too large to send.")
|
||||||
await p.click('#server-tree li[data-path="/home/frodo/lembas.md"] > .tree__more')
|
await p.click('#server-list .frow[data-path="/home/frodo/lembas.md"] .frow__more')
|
||||||
await p.click(".menu .menu__item >> text=Rename")
|
await p.click(".menu .menu__item >> text=Rename")
|
||||||
await p.fill("dialog[open] .dialog__input", "x.md")
|
await p.fill("dialog[open] .dialog__input", "x.md")
|
||||||
await p.click("dialog[open] .btn--primary")
|
await p.click("dialog[open] .btn--primary")
|
||||||
@@ -1394,7 +1394,7 @@ test("H8: at 390 px the server's pane and the switches stay inside the window",
|
|||||||
const p = await page(h8(), { width: 390, height: 844 })
|
const p = await page(h8(), { width: 390, height: 844 })
|
||||||
await p.evaluate(() => document.getElementById("nav-files").click())
|
await p.evaluate(() => document.getElementById("nav-files").click())
|
||||||
await p.click('#files-tabs [data-tab="server"]')
|
await p.click('#files-tabs [data-tab="server"]')
|
||||||
await p.waitForSelector('#server-tree li[data-path="/home/frodo/lembas.md"]')
|
await p.waitForSelector('#server-list .frow[data-path="/home/frodo/lembas.md"]')
|
||||||
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
|
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
|
||||||
await shot(p, "h8-files-390")
|
await shot(p, "h8-files-390")
|
||||||
assert.deepEqual(p.errors, [])
|
assert.deepEqual(p.errors, [])
|
||||||
@@ -1438,22 +1438,22 @@ async function openServerTab(p) {
|
|||||||
await p.click('#files-tabs [data-tab="server"]')
|
await p.click('#files-tabs [data-tab="server"]')
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverRows = (p) => p.$$eval("#server-tree li[data-path]", (l) => l.map((x) => x.dataset.path))
|
const serverRows = (p) => p.$$eval("#server-list .frow[data-path]", (l) => l.map((x) => x.dataset.path))
|
||||||
|
|
||||||
test("N8: the administrator's / is drawn: folders, the mount and the files at the root; Loading… while it is read", async () => {
|
test("N8: the administrator's / is drawn: folders, the mount and the files at the root; Loading… while it is read", async () => {
|
||||||
const p = await page(n8({}, [ROOT_KEY]))
|
const p = await page(n8({}, [ROOT_KEY]))
|
||||||
await openServerTab(p)
|
await openServerTab(p)
|
||||||
await p.waitForSelector("#server-tree .tree__note.is-loading")
|
await p.waitForSelector("#server-list .fnote.is-loading")
|
||||||
assert.equal((await p.textContent("#server-tree .tree__note.is-loading")).trim(), "Loading…")
|
assert.equal((await p.textContent("#server-list .fnote.is-loading")).trim(), "Loading…")
|
||||||
assert.ok(await p.isVisible("#server-tree .tree__note.is-loading"), "the pane is not empty while it waits")
|
assert.ok(await p.isVisible("#server-list .fnote.is-loading"), "the pane is not empty while it waits")
|
||||||
await p.evaluate((k) => window.__mock.release(k), ROOT_KEY)
|
await p.evaluate((k) => window.__mock.release(k), ROOT_KEY)
|
||||||
await p.waitForSelector('#server-tree li[data-path="/test.py"]')
|
await p.waitForSelector('#server-list .frow[data-path="/test.py"]')
|
||||||
assert.deepEqual(await serverRows(p), ["/home", "/shared", "/skills", "/retry_util.py", "/test.py"])
|
assert.deepEqual(await serverRows(p), ["/home", "/shared", "/skills", "/retry_util.py", "/test.py"])
|
||||||
assert.equal(await p.locator("#server-tree .is-loading").count(), 0)
|
assert.equal(await p.locator("#server-list .is-loading").count(), 0)
|
||||||
// The mount opens like a folder; a folder's children come under it.
|
// The mount opens like a folder; a folder's children come under it.
|
||||||
await p.click('#server-tree li[data-path="/home"] > .tree__row')
|
await p.click('#server-list .frow[data-path="/home"] .frow__open')
|
||||||
await p.waitForSelector('#server-tree li[data-path="/home/ada"]')
|
await p.waitForSelector('#server-list .frow[data-path="/home/ada"]')
|
||||||
assert.ok(await p.isVisible('#server-tree li[data-path="/home/ada"]'))
|
assert.ok(await p.isVisible('#server-list .frow[data-path="/home/ada"]'))
|
||||||
await shot(p, "n8-admin-root-1280")
|
await shot(p, "n8-admin-root-1280")
|
||||||
assert.deepEqual(p.errors, [])
|
assert.deepEqual(p.errors, [])
|
||||||
})
|
})
|
||||||
@@ -1461,27 +1461,27 @@ test("N8: the administrator's / is drawn: folders, the mount and the files at th
|
|||||||
test("N8: an answer that has not come in 8 s says so and offers Try again; the late answer is still drawn", async () => {
|
test("N8: an answer that has not come in 8 s says so and offers Try again; the late answer is still drawn", async () => {
|
||||||
const p = await page(n8({}, [ROOT_KEY]), { clock: true })
|
const p = await page(n8({}, [ROOT_KEY]), { clock: true })
|
||||||
await openServerTab(p)
|
await openServerTab(p)
|
||||||
await p.waitForSelector("#server-tree .tree__note.is-loading")
|
await p.waitForSelector("#server-list .fnote.is-loading")
|
||||||
assert.equal(await p.locator("#server-tree .tree__retry").count(), 0)
|
assert.equal(await p.locator("#server-list .fnote__retry").count(), 0)
|
||||||
await p.clock.fastForward(9000)
|
await p.clock.fastForward(9000)
|
||||||
await p.waitForSelector("#server-tree .tree__note.is-loading .tree__retry")
|
await p.waitForSelector("#server-list .fnote.is-loading .fnote__retry")
|
||||||
assert.match(await p.textContent("#server-tree .tree__note.is-loading"), /The server has not answered in 8 seconds\./)
|
assert.match(await p.textContent("#server-list .fnote.is-loading"), /The server has not answered in 8 seconds\./)
|
||||||
await p.evaluate((k) => window.__mock.release(k), ROOT_KEY)
|
await p.evaluate((k) => window.__mock.release(k), ROOT_KEY)
|
||||||
await p.waitForSelector('#server-tree li[data-path="/test.py"]')
|
await p.waitForSelector('#server-list .frow[data-path="/test.py"]')
|
||||||
assert.equal(await p.locator("#server-tree .tree__note").count(), 0)
|
assert.equal(await p.locator("#server-list .fnote").count(), 0)
|
||||||
assert.deepEqual(p.errors, [])
|
assert.deepEqual(p.errors, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
test("N8: a refusal is said in the pane with Try again, which reads the folder afresh", async () => {
|
test("N8: a refusal is said in the pane with Try again, which reads the folder afresh", async () => {
|
||||||
const p = await page(n8({ [ROOT_KEY]: { __error: { status: 403, detail: "This token was not given the app scope." } } }))
|
const p = await page(n8({ [ROOT_KEY]: { __error: { status: 403, detail: "This token was not given the app scope." } } }))
|
||||||
await openServerTab(p)
|
await openServerTab(p)
|
||||||
await p.waitForSelector("#server-tree .tree__note.is-error")
|
await p.waitForSelector("#server-list .fnote.is-error")
|
||||||
assert.match(await p.textContent("#server-tree .tree__note.is-error"), /This token was not given the app scope\./)
|
assert.match(await p.textContent("#server-list .fnote.is-error"), /This token was not given the app scope\./)
|
||||||
assert.equal(await p.getAttribute("#server-tree .tree__note.is-error", "role"), "alert")
|
assert.equal(await p.getAttribute("#server-list .fnote.is-error", "role"), "alert")
|
||||||
await p.evaluate(([k, v]) => window.__mock.answer(k, v), [ROOT_KEY, ADMIN_ROOT])
|
await p.evaluate(([k, v]) => window.__mock.answer(k, v), [ROOT_KEY, ADMIN_ROOT])
|
||||||
await p.click("#server-tree .tree__retry")
|
await p.click("#server-list .fnote__retry")
|
||||||
await p.waitForSelector('#server-tree li[data-path="/home"]')
|
await p.waitForSelector('#server-list .frow[data-path="/home"]')
|
||||||
assert.equal(await p.locator("#server-tree .is-error").count(), 0)
|
assert.equal(await p.locator("#server-list .is-error").count(), 0)
|
||||||
assert.equal((await p.evaluate((k) => window.__mock.api("GET", "/files?path=%2F").length)), 2)
|
assert.equal((await p.evaluate((k) => window.__mock.api("GET", "/files?path=%2F").length)), 2)
|
||||||
assert.deepEqual(p.errors, [])
|
assert.deepEqual(p.errors, [])
|
||||||
})
|
})
|
||||||
@@ -1490,8 +1490,8 @@ test("N8: an answer that is not a listing is said, never an empty pane", async (
|
|||||||
for (const broken of [null, { path: "/" }, { detail: "Not Found" }]) {
|
for (const broken of [null, { path: "/" }, { detail: "Not Found" }]) {
|
||||||
const p = await page(n8({ [ROOT_KEY]: broken }))
|
const p = await page(n8({ [ROOT_KEY]: broken }))
|
||||||
await openServerTab(p)
|
await openServerTab(p)
|
||||||
await p.waitForSelector("#server-tree .tree__note.is-error")
|
await p.waitForSelector("#server-list .fnote.is-error")
|
||||||
assert.match(await p.textContent("#server-tree .tree__note.is-error"), /The server's answer could not be read\./)
|
assert.match(await p.textContent("#server-list .fnote.is-error"), /The server's answer could not be read\./)
|
||||||
assert.deepEqual(p.errors, [])
|
assert.deepEqual(p.errors, [])
|
||||||
await p.context().close()
|
await p.context().close()
|
||||||
}
|
}
|
||||||
@@ -1500,11 +1500,11 @@ test("N8: an answer that is not a listing is said, never an empty pane", async (
|
|||||||
test("N8: Read again while the first answer is on its way draws the listing once", async () => {
|
test("N8: Read again while the first answer is on its way draws the listing once", async () => {
|
||||||
const p = await page(n8({}, [ROOT_KEY]))
|
const p = await page(n8({}, [ROOT_KEY]))
|
||||||
await openServerTab(p)
|
await openServerTab(p)
|
||||||
await p.waitForSelector("#server-tree .tree__note.is-loading")
|
await p.waitForSelector("#server-list .fnote.is-loading")
|
||||||
await p.click("#server-refresh")
|
await p.click("#server-refresh")
|
||||||
await p.waitForFunction((k) => window.__mock.api("GET", "/files?path=%2F").length === 2)
|
await p.waitForFunction((k) => window.__mock.api("GET", "/files?path=%2F").length === 2)
|
||||||
await p.evaluate((k) => window.__mock.release(k), ROOT_KEY)
|
await p.evaluate((k) => window.__mock.release(k), ROOT_KEY)
|
||||||
await p.waitForSelector('#server-tree li[data-path="/test.py"]')
|
await p.waitForSelector('#server-list .frow[data-path="/test.py"]')
|
||||||
await p.waitForTimeout(50)
|
await p.waitForTimeout(50)
|
||||||
assert.deepEqual(await serverRows(p), ["/home", "/shared", "/skills", "/retry_util.py", "/test.py"])
|
assert.deepEqual(await serverRows(p), ["/home", "/shared", "/skills", "/retry_util.py", "/test.py"])
|
||||||
assert.deepEqual(p.errors, [])
|
assert.deepEqual(p.errors, [])
|
||||||
@@ -1513,9 +1513,9 @@ test("N8: Read again while the first answer is on its way draws the listing once
|
|||||||
test("N8: at 390 px the waiting and the refusal stay inside the window, Try again reachable", async () => {
|
test("N8: at 390 px the waiting and the refusal stay inside the window, Try again reachable", async () => {
|
||||||
const p = await page(n8({ [ROOT_KEY]: { __error: { status: 0, detail: "Could not reach the server: connection timed out after a long wait at the very far end of the network" } } }), { width: 390, height: 844 })
|
const p = await page(n8({ [ROOT_KEY]: { __error: { status: 0, detail: "Could not reach the server: connection timed out after a long wait at the very far end of the network" } } }), { width: 390, height: 844 })
|
||||||
await openServerTab(p)
|
await openServerTab(p)
|
||||||
await p.waitForSelector("#server-tree .tree__retry")
|
await p.waitForSelector("#server-list .fnote__retry")
|
||||||
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
|
assert.ok(await noSideways(p, 390), "nothing scrolls sideways")
|
||||||
const box = await p.locator("#server-tree .tree__retry").boundingBox()
|
const box = await p.locator("#server-list .fnote__retry").boundingBox()
|
||||||
assert.ok(box && box.x >= 0 && box.x + box.width <= 390 && box.height >= 28, `Try again is on the screen: ${JSON.stringify(box)}`)
|
assert.ok(box && box.x >= 0 && box.x + box.width <= 390 && box.height >= 28, `Try again is on the screen: ${JSON.stringify(box)}`)
|
||||||
await shot(p, "n8-refused-390")
|
await shot(p, "n8-refused-390")
|
||||||
assert.deepEqual(p.errors, [])
|
assert.deepEqual(p.errors, [])
|
||||||
@@ -1844,6 +1844,125 @@ test("M8: no picture switch where the server cannot draw, or in an agent chat",
|
|||||||
assert.deepEqual(q.errors, [])
|
assert.deepEqual(q.errors, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// FX: the Files page as the web's explorer -- crumbs, the folder tree, the list in columns, the
|
||||||
|
// chosen file's details and preview, using the window's width; narrower, the details and then the
|
||||||
|
// tree give way, and nothing is ever cut off or scrolls the page sideways.
|
||||||
|
|
||||||
|
const LONG = "a-very-long-file-name-that-somebody-chose-for-the-lembas-recipe-and-its-variants.md"
|
||||||
|
function fx() {
|
||||||
|
const cfg = n8({
|
||||||
|
"GET /files?path=%2Fhome": { path: "/home", parent: "/", entries: [{ name: "ada", path: "/home/ada", kind: "dir", size: 0, mime: "", updated_at: STAMP }, { name: LONG, path: `/home/${LONG}`, kind: "file", size: 123456, mime: "text/markdown", updated_at: STAMP }] },
|
||||||
|
"GET /files/content": { path: "/test.py", name: "test.py", size: 210, mime: "text/x-python", kind: "code", language: "python", sha: "x", text: "print('lembas')\n" },
|
||||||
|
})
|
||||||
|
cfg.commands = {
|
||||||
|
local_list: { root: "/home/dev/project", path: "", more: false, entries: [{ name: "src", path: "src", kind: "dir", size: 0, modified: 1767323045 }, { name: "README.md", path: "README.md", kind: "file", size: 2048, modified: 1767323045 }] },
|
||||||
|
local_read: { root: "/home/dev/project", path: "README.md", name: "README.md", size: 2048, text: "# Project\n", withheld: null },
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
test("FX: wide, the server's files are a tree, a list in columns and the chosen file's details with a preview", async () => {
|
||||||
|
const p = await page(fx(), { width: 1600, height: 900 })
|
||||||
|
await openServerTab(p)
|
||||||
|
await p.waitForSelector('#server-list .frow[data-path="/test.py"]')
|
||||||
|
// The tree: the top, then its folders; the top is where we are.
|
||||||
|
await p.waitForSelector('#server-tree li[data-path="/home"]')
|
||||||
|
assert.equal(await p.textContent("#server-tree .ftree__root"), "All files")
|
||||||
|
assert.equal(await p.getAttribute("#server-tree .ftree__root", "aria-current"), "true")
|
||||||
|
assert.deepEqual(await p.$$eval("#server-tree li[data-path]", (l) => l.map((x) => x.dataset.path)), ["/home", "/shared", "/skills"])
|
||||||
|
// The columns, and the kinds' words.
|
||||||
|
assert.deepEqual(await p.$$eval("#server-list .frow--head [role=columnheader]", (l) => l.map((x) => x.textContent)), ["Name", "Type", "Size", "Changed", "Actions"])
|
||||||
|
assert.equal(await p.textContent('#server-list .frow[data-path="/shared"] .frow__type'), "mount")
|
||||||
|
assert.equal(await p.textContent('#server-list .frow[data-path="/home"] .frow__type'), "folder")
|
||||||
|
assert.equal(await p.textContent('#server-list .frow[data-path="/test.py"] .frow__type'), "py")
|
||||||
|
assert.equal(await p.textContent('#server-list .frow[data-path="/test.py"] .frow__size'), "210 B")
|
||||||
|
// Every row's cells on the same tracks: the size column's left edge is one x for every row.
|
||||||
|
const xs = await p.$$eval("#server-list .frow .frow__size", (l) => [...new Set(l.map((x) => Math.round(x.getBoundingClientRect().right)))])
|
||||||
|
assert.equal(xs.length, 1, `sizes end on one line: ${xs}`)
|
||||||
|
// A file chosen: its details and the start of it.
|
||||||
|
assert.match(await p.textContent("#server-details"), /Choose a file to see it here\./)
|
||||||
|
await p.click('#server-list .frow[data-path="/test.py"] .frow__open')
|
||||||
|
await p.waitForSelector("#server-details .fdetails__text")
|
||||||
|
assert.equal(await p.textContent("#server-details .fdetails__name"), "test.py")
|
||||||
|
assert.equal(await p.textContent("#server-details .fdetails__text"), "print('lembas')\n")
|
||||||
|
assert.ok(await p.$eval('#server-list .frow[data-path="/test.py"]', (x) => x.classList.contains("is-chosen")))
|
||||||
|
assert.ok(await p.isHidden("#viewer"), "choosing is not opening")
|
||||||
|
await shot(p, "fx-server-1600")
|
||||||
|
// Open in the viewer from the details, and with a double click.
|
||||||
|
await p.click("#server-details .btn--primary")
|
||||||
|
await p.waitForSelector("#viewer:not([hidden])")
|
||||||
|
assert.equal(await p.textContent("#viewer-name"), "test.py")
|
||||||
|
assert.deepEqual(p.errors, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("FX: a folder is listed from the list, the tree or the crumbs; the crumbs say where", async () => {
|
||||||
|
const p = await page(fx(), { width: 1280, height: 860 })
|
||||||
|
await openServerTab(p)
|
||||||
|
await p.waitForSelector('#server-list .frow[data-path="/home"]')
|
||||||
|
await p.click('#server-list .frow[data-path="/home"] .frow__open')
|
||||||
|
await p.waitForSelector(`#server-list .frow[data-path="/home/${LONG}"]`)
|
||||||
|
assert.deepEqual(await p.$$eval("#server-crumbs > *:not(.crumbs__sep)", (l) => l.map((x) => x.textContent)), ["All files", "home"])
|
||||||
|
assert.equal(await p.getAttribute('#server-tree .ftree__row[data-path="/home"]', "aria-current"), "true")
|
||||||
|
// A long name gives, the rest of its row stays on its tracks and inside the list.
|
||||||
|
const row = await p.$eval(`#server-list .frow[data-path="/home/${LONG}"]`, (r) => {
|
||||||
|
const list = document.getElementById("server-list").getBoundingClientRect()
|
||||||
|
const more = r.querySelector(".frow__when").getBoundingClientRect()
|
||||||
|
return { right: more.right, list: list.right, label: r.querySelector(".frow__label").scrollWidth > r.querySelector(".frow__label").clientWidth }
|
||||||
|
})
|
||||||
|
assert.ok(row.right <= row.list && row.label, `the long name is cut with an ellipsis, the date inside: ${JSON.stringify(row)}`)
|
||||||
|
await p.click("#server-crumbs .crumbs__link")
|
||||||
|
await p.waitForSelector('#server-list .frow[data-path="/test.py"]')
|
||||||
|
await p.click('#server-tree .ftree__row[data-path="/home"]')
|
||||||
|
await p.waitForSelector(`#server-list .frow[data-path="/home/${LONG}"]`)
|
||||||
|
await p.waitForSelector('#server-tree li[data-path="/home/ada"]')
|
||||||
|
await shot(p, "fx-server-1280")
|
||||||
|
assert.deepEqual(p.errors, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("FX: this computer's folder in the same shape, its dates from the system's", async () => {
|
||||||
|
const p = await page(fx(), { width: 1600, height: 900 })
|
||||||
|
await p.evaluate(() => document.getElementById("nav-files").click())
|
||||||
|
await p.waitForSelector('#local-list .frow[data-path="README.md"]')
|
||||||
|
assert.equal(await p.textContent("#local-tree .ftree__root"), "project")
|
||||||
|
assert.equal(await p.textContent("#local-crumbs .crumbs__here"), "project")
|
||||||
|
assert.notEqual((await p.textContent('#local-list .frow[data-path="README.md"] .frow__when')).trim(), "")
|
||||||
|
await p.click('#local-list .frow[data-path="README.md"] .frow__open')
|
||||||
|
await p.waitForSelector("#local-details .fdetails__text")
|
||||||
|
assert.equal(await p.textContent("#local-details .fdetails__text"), "# Project\n")
|
||||||
|
await shot(p, "fx-local-1600")
|
||||||
|
assert.deepEqual(p.errors, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("FX: at every width nothing scrolls sideways and nothing is cut off; narrower, a file opens in the viewer", async () => {
|
||||||
|
for (const [width, height] of [[1920, 1080], [1280, 860], [1024, 768], [800, 700], [390, 844]]) {
|
||||||
|
const p = await page(fx(), { width, height })
|
||||||
|
await openServerTab(p)
|
||||||
|
await p.waitForSelector('#server-list .frow[data-path="/home"]')
|
||||||
|
await p.click('#server-list .frow[data-path="/home"] .frow__open')
|
||||||
|
await p.waitForSelector(`#server-list .frow[data-path="/home/${LONG}"]`)
|
||||||
|
assert.ok(await noSideways(p, width), `${width}: nothing scrolls sideways`)
|
||||||
|
// Every visible control of the bar and the rows is inside the window.
|
||||||
|
const out = await p.$$eval("#pane-server button, #pane-server .frow__size", (l, w) => l.filter((b) => { const r = b.getBoundingClientRect(); return r.width > 0 && (r.left < -1 || r.right > w + 1) }).map((b) => b.className || b.id), width)
|
||||||
|
assert.deepEqual(out, [], `${width}: controls inside the window`)
|
||||||
|
// The panes give way by the room the page has (60rem, 40rem of it), not the window's width.
|
||||||
|
const room = await p.$eval("#pane-server", (x) => x.getBoundingClientRect().width)
|
||||||
|
const details = await p.isVisible("#server-details")
|
||||||
|
assert.equal(details, room > 960, `${width}: the details pane at ${room} px`)
|
||||||
|
assert.equal(await p.isVisible("#server-tree"), room > 640, `${width}: the tree at ${room} px`)
|
||||||
|
// The name's track never closes: every name shows at least a few letters.
|
||||||
|
const names = await p.$$eval("#server-list .frow__label", (l) => Math.min(...l.map((x) => x.getBoundingClientRect().width)))
|
||||||
|
assert.ok(names >= 24, `${width}: names keep room (${names} px)`)
|
||||||
|
await shot(p, `fx-server-${width}`)
|
||||||
|
await p.click("#server-crumbs .crumbs__link")
|
||||||
|
await p.waitForSelector('#server-list .frow[data-path="/test.py"]')
|
||||||
|
await p.click('#server-list .frow[data-path="/test.py"] .frow__open')
|
||||||
|
if (details) await p.waitForSelector("#server-details .fdetails__text")
|
||||||
|
else await p.waitForSelector("#viewer:not([hidden])")
|
||||||
|
assert.deepEqual(p.errors, [])
|
||||||
|
await p.context().close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// --- Run ----------------------------------------------------------------------------------------
|
// --- Run ----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
const only = process.argv[2] || ""
|
const only = process.argv[2] || ""
|
||||||
|
|||||||
+189
-40
@@ -1414,57 +1414,206 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
|||||||
}
|
}
|
||||||
.tabs__tab[aria-selected="true"] { color: var(--ink); background: var(--surface-active); }
|
.tabs__tab[aria-selected="true"] { color: var(--ink); background: var(--surface-active); }
|
||||||
|
|
||||||
.files { display: grid; align-content: start; }
|
/* --- Files: two explorers in the web's shape ------------------------------------------------
|
||||||
.files__pane { display: grid; gap: var(--sp-2); background: var(--surface); border: var(--border-w) solid var(--border); border-radius: var(--radius-soft); padding: var(--sp-3); min-width: 0; }
|
The page's whole width under its head, like a file manager: where you are and what can be done
|
||||||
.files__head { display: grid; grid-template-columns: minmax(0, 1fr) var(--control-h-sm); gap: var(--sp-2); align-items: center; }
|
there, then the folder tree, the list and the chosen file's details side by side, each its own
|
||||||
.files__where { min-width: 0; }
|
scroller. The list is one grid with a track per column and every row a subgrid of it, so a long
|
||||||
/* The server's pane: Upload and New folder before Read again. */
|
name never pushes the type, size or date out of line: the name's track is the one that gives.
|
||||||
#pane-server .files__head { grid-template-columns: minmax(0, 1fr) repeat(3, var(--control-h-sm)); }
|
Narrower, the details go first (a file then opens in the viewer), then the tree (the crumbs
|
||||||
#server-status { white-space: pre-line; }
|
are the way up), then the type and date columns. */
|
||||||
.files__where select { width: 100%; height: var(--control-h-sm); }
|
.page__body.files { display: grid; grid-template-rows: minmax(0, 1fr); padding: 0; overflow: hidden; }
|
||||||
.files__tree { min-height: 0; }
|
.page__body.files > * { max-width: none; margin: 0; }
|
||||||
.pane__path { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; direction: rtl; text-align: left; font: var(--text-xs) var(--font-ui); color: var(--ink-muted); }
|
.fx { display: grid; grid-template-rows: auto auto minmax(0, 1fr); min-width: 0; min-height: 0; container: fx / inline-size; }
|
||||||
.pane__pick { width: 100%; }
|
.fx > .fx__bar { grid-row: 1; }
|
||||||
.pane__note { padding: var(--sp-2) 0; font-size: var(--text-sm); color: var(--ink-muted); }
|
.fx > .fx__status { grid-row: 2; }
|
||||||
.files__tree > .btn { justify-self: start; }
|
.fx > .fx__panes { grid-row: 3; }
|
||||||
|
.fx[hidden] { display: none; }
|
||||||
.tree, .tree ul { list-style: none; margin: 0; padding: 0; }
|
.fx__bar {
|
||||||
.tree__row {
|
|
||||||
width: 100%;
|
|
||||||
height: var(--control-h-sm);
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: calc(var(--depth, 0) * var(--sp-4)) 1rem 1rem minmax(0, 1fr) auto;
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
grid-template-areas: "where crumbs tools";
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
min-height: var(--header-height);
|
||||||
|
padding: var(--sp-1) var(--sp-4);
|
||||||
|
border-bottom: var(--border-w) solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
}
|
||||||
|
.fx__where { grid-area: where; min-width: 0; max-width: 18rem; }
|
||||||
|
.fx__bar > .crumbs { grid-area: crumbs; }
|
||||||
|
.fx__where:empty { display: none; }
|
||||||
|
.fx__tools { grid-area: tools; display: flex; align-items: center; gap: var(--sp-1); }
|
||||||
|
.fx__status { margin: 0; padding: var(--sp-2) var(--sp-4); border-bottom: var(--border-w) solid var(--border); white-space: pre-line; }
|
||||||
|
.fx__status[hidden] { display: none; }
|
||||||
|
.pane__pick { width: 100%; height: var(--control-h-sm); }
|
||||||
|
|
||||||
|
.crumbs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-1);
|
||||||
|
min-width: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
scrollbar-width: none;
|
||||||
|
font: var(--text-sm) var(--font-mono);
|
||||||
|
}
|
||||||
|
.crumbs__sep { color: var(--ink-faint); }
|
||||||
|
.crumbs__link {
|
||||||
|
min-height: var(--control-h-sm);
|
||||||
|
padding: 0 var(--sp-1);
|
||||||
|
font: inherit;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.crumbs__link:hover { color: var(--ink); background: var(--surface-hover); }
|
||||||
|
.crumbs__here { padding: 0 var(--sp-1); color: var(--ink); font-weight: 600; }
|
||||||
|
|
||||||
|
.fx__panes { display: grid; grid-template-columns: minmax(11rem, 14rem) minmax(0, 1fr) minmax(14rem, 18rem); min-width: 0; min-height: 0; }
|
||||||
|
.fx__tree, .fx__list, .fx__details { min-width: 0; min-height: 0; overflow: auto; overscroll-behavior: contain; }
|
||||||
|
.fx__tree { padding: var(--sp-2); border-right: var(--border-w) solid var(--border); background: var(--surface); }
|
||||||
|
.fx__list { padding: 0 var(--sp-4) var(--sp-8); container: fxlist / inline-size; }
|
||||||
|
.fx__details { display: grid; align-content: start; gap: var(--sp-3); padding: var(--sp-4); border-left: var(--border-w) solid var(--border); background: var(--surface); }
|
||||||
|
.fx[data-empty="true"] .fx__tree, .fx[data-empty="true"] .fx__details { display: none; }
|
||||||
|
.fx[data-empty="true"] .fx__panes { grid-template-columns: minmax(0, 1fr); }
|
||||||
|
|
||||||
|
/* The folder tree: a chevron opens a folder, its name lists it. */
|
||||||
|
.ftree, .ftree ul { list-style: none; margin: 0; padding: 0; }
|
||||||
|
.ftree__line { display: grid; grid-template-columns: calc(var(--depth, 0) * var(--sp-3)) 1.25rem minmax(0, 1fr); align-items: center; }
|
||||||
|
.ftree__line::before { content: ""; }
|
||||||
|
.ftree__chevron { display: grid; place-items: center; width: 1.25rem; height: var(--control-h-sm); padding: 0; color: var(--ink-faint); background: transparent; border: 0; cursor: pointer; }
|
||||||
|
.ftree__chevron .icon { width: 12px; height: 12px; transition: transform var(--dur-1) var(--ease-out); }
|
||||||
|
.ftree__chevron[aria-expanded="true"] .icon { transform: rotate(90deg); }
|
||||||
|
.ftree__row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1rem minmax(0, 1fr);
|
||||||
gap: var(--sp-2);
|
gap: var(--sp-2);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-height: var(--control-h-sm);
|
||||||
padding: 0 var(--sp-2);
|
padding: 0 var(--sp-2);
|
||||||
text-align: left;
|
|
||||||
font: var(--text-sm) var(--font-body);
|
font: var(--text-sm) var(--font-body);
|
||||||
|
text-align: left;
|
||||||
|
color: var(--ink-muted);
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ftree__row:hover { color: var(--ink); background: var(--surface-hover); }
|
||||||
|
.ftree__row.is-current { color: var(--ink); background: var(--surface-active); font-weight: 600; }
|
||||||
|
.ftree__root { margin-bottom: var(--sp-1); }
|
||||||
|
.ftree__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
|
|
||||||
|
/* The list. */
|
||||||
|
.frows {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(3.5rem, auto) minmax(4.5rem, auto) minmax(6.5rem, auto) var(--control-h-sm);
|
||||||
|
column-gap: var(--sp-4);
|
||||||
|
}
|
||||||
|
.frow {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
align-items: center;
|
||||||
|
min-height: var(--row-h);
|
||||||
|
margin: 0 calc(-1 * var(--sp-2));
|
||||||
|
padding: 0 var(--sp-2);
|
||||||
|
border-bottom: var(--border-w) solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
.frow:not(.frow--head):hover { background: var(--surface-hover); }
|
||||||
|
.frow.is-chosen { background: var(--surface-active); }
|
||||||
|
.frow--head {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--ink-faint);
|
||||||
|
font: var(--text-xs) var(--font-ui);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.frow__name { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
|
||||||
|
.frow__open {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-3);
|
||||||
|
min-width: 0;
|
||||||
|
min-height: var(--row-h);
|
||||||
|
padding: 0;
|
||||||
|
font: var(--text-sm) var(--font-body);
|
||||||
|
text-align: left;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
/* The indent's track needs an item of its own, or the chevron would sit in it. */
|
/* The whole row opens what it names; the buttons at its end stay on top. */
|
||||||
.tree__row::before { content: ""; }
|
.frow__open::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
|
||||||
.tree__row:hover { background: var(--surface-hover); }
|
.frow__open:focus-visible { outline: none; }
|
||||||
.tree__row .icon { color: var(--ink-muted); }
|
.frow__open:focus-visible::after { outline: var(--outline-w) solid var(--accent); outline-offset: calc(-1 * var(--outline-w)); }
|
||||||
.tree__chevron { display: grid; place-items: center; width: 1rem; }
|
.frow__name .badge, .frow__actions { position: relative; z-index: 1; }
|
||||||
.tree__chevron .icon { width: 12px; height: 12px; transition: transform var(--dur-1) var(--ease-out); }
|
.frow__icon { display: inline-flex; color: var(--ink-muted); }
|
||||||
.tree__row[aria-expanded="true"] .tree__chevron .icon { transform: rotate(90deg); }
|
.frow__icon--dir, .frow__icon--mount { color: var(--accent); }
|
||||||
.tree__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
.frow__icon--code, .frow__icon--html { color: var(--leaf); }
|
||||||
.tree__size { font: var(--text-xs) var(--font-ui); color: var(--ink-faint); }
|
.frow__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
/* A row with a more button beside it: the button on the row's own line, its folder below both. */
|
.frow__type { font: var(--text-xs) var(--font-mono); color: var(--ink-faint); white-space: nowrap; }
|
||||||
.tree__item { display: grid; grid-template-columns: minmax(0, 1fr) var(--control-h-sm); align-items: center; }
|
.frow__size, .frow__when { font-size: var(--text-sm); color: var(--ink-muted); white-space: nowrap; text-align: right; }
|
||||||
.tree__item > ul { grid-column: 1 / -1; }
|
.frow--head .frow__size, .frow--head .frow__when { font-size: var(--text-xs); }
|
||||||
.tree__more { opacity: 0; }
|
.frow__actions { display: flex; justify-content: flex-end; }
|
||||||
.tree__item:hover > .tree__more, .tree__more:focus-visible, .tree__more[aria-expanded="true"] { opacity: 1; }
|
.frow__more { opacity: 0; }
|
||||||
@media (hover: none) { .tree__more { opacity: 1; } }
|
.frow:hover .frow__more, .frow__more:focus-visible, .frow__more[aria-expanded="true"] { opacity: 1; }
|
||||||
.tree__note { padding: var(--sp-1) var(--sp-3); padding-inline-start: calc(var(--sp-3) + var(--depth, 0) * var(--sp-4)); font-size: var(--text-xs); color: var(--ink-muted); }
|
@media (hover: none) { .frow__more { opacity: 1; } }
|
||||||
|
.badge { padding: 0 var(--sp-1); font: var(--text-xs) var(--font-ui); color: var(--ink-muted); border: var(--border-w) solid var(--border); border-radius: var(--radius-sm); white-space: nowrap; }
|
||||||
|
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
||||||
|
.fx__empty { display: grid; justify-items: center; gap: var(--sp-3); padding: var(--sp-12) var(--sp-4); text-align: center; color: var(--ink-muted); }
|
||||||
|
.fx__empty .icon { width: 24px; height: 24px; color: var(--leaf); }
|
||||||
|
|
||||||
|
/* The chosen file. */
|
||||||
|
.fdetails__none { display: grid; justify-items: center; gap: var(--sp-2); padding: var(--sp-8) 0; text-align: center; color: var(--ink-muted); font-size: var(--text-sm); }
|
||||||
|
.fdetails__none .icon { width: 24px; height: 24px; color: var(--ink-faint); }
|
||||||
|
.fdetails__id { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-2); align-items: center; }
|
||||||
|
.fdetails__name { font: 600 var(--text-base) / var(--leading-tight) var(--font-body); overflow-wrap: anywhere; }
|
||||||
|
.fdetails__facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-1) var(--sp-3); margin: 0; font-size: var(--text-sm); }
|
||||||
|
.fdetails__facts dt { color: var(--ink-faint); }
|
||||||
|
.fdetails__facts dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
|
||||||
|
.fdetails__do { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
|
||||||
|
.fdetails__preview { min-width: 0; }
|
||||||
|
.fdetails__text { margin: 0; max-height: 24rem; overflow: auto; padding: var(--sp-3); font: var(--text-xs) / var(--leading-normal) var(--font-mono); white-space: pre; color: var(--ink); background: var(--bg); border: var(--border-w) solid var(--border); border-radius: var(--radius-sm); }
|
||||||
|
.fdetails__note { font-size: var(--text-sm); color: var(--ink-muted); }
|
||||||
|
|
||||||
/* Loading, still waiting, refused: the words, then Try again on the same line when it fits. */
|
/* Loading, still waiting, refused: the words, then Try again on the same line when it fits. */
|
||||||
.tree__note.is-loading, .tree__note.is-error { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
|
.fnote { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); padding: var(--sp-2) var(--sp-1); padding-inline-start: calc(var(--sp-1) + var(--depth, 0) * var(--sp-3)); font-size: var(--text-sm); color: var(--ink-muted); }
|
||||||
.tree__note > span { min-width: 0; overflow-wrap: anywhere; }
|
.ftree .fnote { font-size: var(--text-xs); }
|
||||||
.tree__retry { flex: none; }
|
.fnote.is-error { color: var(--warning); }
|
||||||
|
.fnote > span { min-width: 0; overflow-wrap: anywhere; }
|
||||||
|
.fnote__retry { flex: none; }
|
||||||
|
|
||||||
|
/* By the room the page has, not the window's: the sidebar takes its share first. */
|
||||||
|
@container fx (max-width: 60rem) {
|
||||||
|
.fx__panes { grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr); }
|
||||||
|
.fx__details { display: none; }
|
||||||
|
}
|
||||||
|
@container fx (max-width: 40rem) {
|
||||||
|
.fx__panes { grid-template-columns: minmax(0, 1fr); }
|
||||||
|
.fx__tree { display: none; }
|
||||||
|
.fx__bar { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "where where" "crumbs tools"; padding: var(--sp-1) var(--sp-3); }
|
||||||
|
.fx__where { max-width: none; }
|
||||||
|
.fx__list { padding: 0 var(--sp-3) var(--sp-8); }
|
||||||
|
}
|
||||||
|
/* A list narrower than its columns keeps the name and the size; the type and date go. */
|
||||||
|
@container fxlist (max-width: 28rem) {
|
||||||
|
.frows { grid-template-columns: minmax(0, 1fr) auto var(--control-h-sm); column-gap: var(--sp-2); }
|
||||||
|
.frows .frow__type, .frows .frow__when { display: none; }
|
||||||
|
}
|
||||||
|
@media (max-width: 48rem) {
|
||||||
|
.frow, .frow__open { min-height: var(--tap-min); }
|
||||||
|
}
|
||||||
|
|
||||||
/* An empty page: an icon, a heading, a sentence, centred. */
|
/* An empty page: an icon, a heading, a sentence, centred. */
|
||||||
.empty { display: grid; justify-items: center; gap: var(--sp-3); padding: var(--sp-12) var(--sp-4) var(--sp-4); text-align: center; }
|
.empty { display: grid; justify-items: center; gap: var(--sp-3); padding: var(--sp-12) var(--sp-4) var(--sp-4); text-align: center; }
|
||||||
|
|||||||
+136
@@ -47,6 +47,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastního modelu běží na prvním volném z těchto, v tomto pořadí. Žádný nezaškrtnutý: všechny.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastního modelu běží na prvním volném z těchto, v tomto pořadí. Žádný nezaškrtnutý: všechny.",
|
||||||
"About": "O aplikaci",
|
"About": "O aplikaci",
|
||||||
"Account": "Účet",
|
"Account": "Účet",
|
||||||
|
"Actions": "Akce",
|
||||||
"Activity": "Aktivita",
|
"Activity": "Aktivita",
|
||||||
"Add": "Přidat",
|
"Add": "Přidat",
|
||||||
"Add folder…": "Přidat složku…",
|
"Add folder…": "Přidat složku…",
|
||||||
@@ -54,6 +55,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Agent",
|
"Agent": "Agent",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Agentní konverzace mohou pracovat v těchto složkách a ve všem uvnitř nich — nikde jinde na tomto počítači.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Agentní konverzace mohou pracovat v těchto složkách a ve všem uvnitř nich — nikde jinde na tomto počítači.",
|
||||||
"Agent: %(why)s": "Agent: %(why)s",
|
"Agent: %(why)s": "Agent: %(why)s",
|
||||||
|
"All files": "Všechny soubory",
|
||||||
"Allow": "Povolit",
|
"Allow": "Povolit",
|
||||||
"Always": "Vždy",
|
"Always": "Vždy",
|
||||||
"Always allow": "Vždy povolit",
|
"Always allow": "Vždy povolit",
|
||||||
@@ -77,10 +79,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Úlohy na pozadí",
|
"Background tasks": "Úlohy na pozadí",
|
||||||
"Cancel": "Zrušit",
|
"Cancel": "Zrušit",
|
||||||
"Carry it out": "Provést to",
|
"Carry it out": "Provést to",
|
||||||
|
"Changed": "Změněno",
|
||||||
"Chat": "Konverzace",
|
"Chat": "Konverzace",
|
||||||
"Chats": "Konverzace",
|
"Chats": "Konverzace",
|
||||||
"Check for updates…": "Zkontrolovat aktualizace…",
|
"Check for updates…": "Zkontrolovat aktualizace…",
|
||||||
"Choose": "Vybrat",
|
"Choose": "Vybrat",
|
||||||
|
"Choose a file to see it here.": "Vyberte soubor a uvidíte ho tady.",
|
||||||
"Choose a folder first.": "Nejprve vyberte složku.",
|
"Choose a folder first.": "Nejprve vyberte složku.",
|
||||||
"Choose a folder…": "Vybrat složku…",
|
"Choose a folder…": "Vybrat složku…",
|
||||||
"Choose a model to choose its effort.": "Vyberte model, abyste mohli zvolit jeho náročnost.",
|
"Choose a model to choose its effort.": "Vyberte model, abyste mohli zvolit jeho náročnost.",
|
||||||
@@ -101,6 +105,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Připojeno.",
|
"Connected.": "Připojeno.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Připojeno. Tento shell je otevřený i na jiné kartě a sdílejí velikost.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Připojeno. Tento shell je otevřený i na jiné kartě a sdílejí velikost.",
|
||||||
"Connecting…": "Připojuje se…",
|
"Connecting…": "Připojuje se…",
|
||||||
|
"Contents": "Obsah",
|
||||||
"Context": "Kontext",
|
"Context": "Kontext",
|
||||||
"Context and background tasks": "Kontext a úlohy na pozadí",
|
"Context and background tasks": "Kontext a úlohy na pozadí",
|
||||||
"Context used": "Využitý kontext",
|
"Context used": "Využitý kontext",
|
||||||
@@ -122,6 +127,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "Smazat tuto konverzaci?",
|
"Delete this chat?": "Smazat tuto konverzaci?",
|
||||||
"Delete this scheduled task?": "Odstranit tuto naplánovanou úlohu?",
|
"Delete this scheduled task?": "Odstranit tuto naplánovanou úlohu?",
|
||||||
"Deny": "Zamítnout",
|
"Deny": "Zamítnout",
|
||||||
|
"Details": "Podrobnosti",
|
||||||
"Dictate a message": "Nadiktovat zprávu",
|
"Dictate a message": "Nadiktovat zprávu",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Odpojeno. Pro opětovné připojení zavřete a znovu otevřete.",
|
"Disconnected. Close and reopen to reconnect.": "Odpojeno. Pro opětovné připojení zavřete a znovu otevřete.",
|
||||||
"Done": "Hotovo",
|
"Done": "Hotovo",
|
||||||
@@ -155,10 +161,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Název složky",
|
"Folder name": "Název složky",
|
||||||
"Folder on %(device)s": "Složka na zařízení %(device)s",
|
"Folder on %(device)s": "Složka na zařízení %(device)s",
|
||||||
"Folder on the computer": "Složka na počítači",
|
"Folder on the computer": "Složka na počítači",
|
||||||
|
"Folders": "Složky",
|
||||||
"Free space": "Volné místo",
|
"Free space": "Volné místo",
|
||||||
"Hello": "Dobrý den",
|
"Hello": "Dobrý den",
|
||||||
"Hello, %(name)s": "Dobrý den, %(name)s",
|
"Hello, %(name)s": "Dobrý den, %(name)s",
|
||||||
"History": "Historie",
|
"History": "Historie",
|
||||||
|
"Home": "Domů",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Kolik jich na ní pracuje současně, včetně vlastního agenta běhu, na stejném modelu.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Kolik jich na ní pracuje současně, včetně vlastního agenta běhu, na stejném modelu.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Vlastními slovy. Server z nich vytvoří pravidlo a pokyn a před uložením vám ukáže další spuštění.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Vlastními slovy. Server z nich vytvoří pravidlo a pokyn a před uložením vám ukáže další spuštění.",
|
||||||
"Install LLeMbas %(version)s…": "Nainstalovat LLeMbas %(version)s…",
|
"Install LLeMbas %(version)s…": "Nainstalovat LLeMbas %(version)s…",
|
||||||
@@ -197,10 +205,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modely",
|
"Models": "Modely",
|
||||||
"Models for scheduled tasks": "Modely pro naplánované úlohy",
|
"Models for scheduled tasks": "Modely pro naplánované úlohy",
|
||||||
"More": "Více",
|
"More": "Více",
|
||||||
|
"More for %(name)s": "Další pro %(name)s",
|
||||||
"Move to folder…": "Přesunout do složky…",
|
"Move to folder…": "Přesunout do složky…",
|
||||||
"Move to the trash": "Přesunout do koše",
|
"Move to the trash": "Přesunout do koše",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "Přesunout „%(name)s“ do koše? Odtamtud ho lze obnovit.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "Přesunout „%(name)s“ do koše? Odtamtud ho lze obnovit.",
|
||||||
"Moved to the trash.": "Přesunuto do koše.",
|
"Moved to the trash.": "Přesunuto do koše.",
|
||||||
|
"Name": "Název",
|
||||||
"Never": "Nikdy",
|
"Never": "Nikdy",
|
||||||
"New agent chat": "Nová agentní konverzace",
|
"New agent chat": "Nová agentní konverzace",
|
||||||
"New chat": "Nová konverzace",
|
"New chat": "Nová konverzace",
|
||||||
@@ -225,6 +235,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Zaznamenal model",
|
"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 here yet. Not even a hobbit hole.": "Zatím tu nic není. Ani hobití nora.",
|
||||||
"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 remembered yet.": "Zatím si nic nepamatuje.",
|
||||||
@@ -250,6 +261,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "Nebo napište odpověď",
|
"Or type an answer": "Nebo napište odpověď",
|
||||||
"Outputs": "Výstupy",
|
"Outputs": "Výstupy",
|
||||||
"Page": "Stránka",
|
"Page": "Stránka",
|
||||||
|
"Path": "Cesta",
|
||||||
"Pause": "Pozastavit",
|
"Pause": "Pozastavit",
|
||||||
"Pause %(title)s": "Pozastavit %(title)s",
|
"Pause %(title)s": "Pozastavit %(title)s",
|
||||||
"Paused": "Pozastaveno",
|
"Paused": "Pozastaveno",
|
||||||
@@ -316,6 +328,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Přihlášen jako %(who)s.",
|
"Signed in as %(who)s.": "Přihlášen jako %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Velikost",
|
||||||
"Skills": "Dovednosti",
|
"Skills": "Dovednosti",
|
||||||
"Skipped": "Vynecháno",
|
"Skipped": "Vynecháno",
|
||||||
"Something to remember…": "Něco k zapamatování…",
|
"Something to remember…": "Něco k zapamatování…",
|
||||||
@@ -412,6 +425,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Nástroje",
|
"Tools": "Nástroje",
|
||||||
"Tools and skills…": "Nástroje a dovednosti…",
|
"Tools and skills…": "Nástroje a dovednosti…",
|
||||||
"Try again": "Zkusit znovu",
|
"Try again": "Zkusit znovu",
|
||||||
|
"Type": "Typ",
|
||||||
"Type a command or a chat's title": "Napište příkaz nebo název konverzace",
|
"Type a command or a chat's title": "Napište příkaz nebo název konverzace",
|
||||||
"Type a folder…": "Zadat složku…",
|
"Type a folder…": "Zadat složku…",
|
||||||
"Unpin chat": "Odepnout konverzaci",
|
"Unpin chat": "Odepnout konverzaci",
|
||||||
@@ -433,6 +447,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Kdy",
|
"When": "Kdy",
|
||||||
"When it matters": "Když na tom záleží",
|
"When it matters": "Když na tom záleží",
|
||||||
"Where it runs": "Kde se spouští",
|
"Where it runs": "Kde se spouští",
|
||||||
|
"Where you are": "Kde jste",
|
||||||
"Wide": "Široký",
|
"Wide": "Široký",
|
||||||
"Writing": "Píše",
|
"Writing": "Píše",
|
||||||
"Writing down…": "Zapisuji…",
|
"Writing down…": "Zapisuji…",
|
||||||
@@ -468,6 +483,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "selhalo",
|
"failed": "selhalo",
|
||||||
"findings: %(n)s": "zjištění: %(n)s",
|
"findings: %(n)s": "zjištění: %(n)s",
|
||||||
"finished": "dokončený",
|
"finished": "dokončený",
|
||||||
|
"folder": "složka",
|
||||||
"free": "volný",
|
"free": "volný",
|
||||||
"idle": "nečinný",
|
"idle": "nečinný",
|
||||||
"it serves one request at a time and is answering another": "vyřizuje jeden požadavek najednou a právě vyřizuje jiný",
|
"it serves one request at a time and is answering another": "vyřizuje jeden požadavek najednou a právě vyřizuje jiný",
|
||||||
@@ -475,6 +491,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "se zpožděním",
|
"late": "se zpožděním",
|
||||||
"light": "světlý",
|
"light": "světlý",
|
||||||
"messages: %(n)s": "zprávy: %(n)s",
|
"messages: %(n)s": "zprávy: %(n)s",
|
||||||
|
"mount": "připojení",
|
||||||
"not linked": "nepropojeno",
|
"not linked": "nepropojeno",
|
||||||
"not linked now": "teď není propojený",
|
"not linked now": "teď není propojený",
|
||||||
"off": "vypnuto",
|
"off": "vypnuto",
|
||||||
@@ -534,6 +551,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Eine Aufgabe ohne eigenes Modell läuft auf dem ersten freien davon, in dieser Reihenfolge. Keines angehakt: alle.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Eine Aufgabe ohne eigenes Modell läuft auf dem ersten freien davon, in dieser Reihenfolge. Keines angehakt: alle.",
|
||||||
"About": "Über",
|
"About": "Über",
|
||||||
"Account": "Konto",
|
"Account": "Konto",
|
||||||
|
"Actions": "Aktionen",
|
||||||
"Activity": "Aktivität",
|
"Activity": "Aktivität",
|
||||||
"Add": "Hinzufügen",
|
"Add": "Hinzufügen",
|
||||||
"Add folder…": "Ordner hinzufügen…",
|
"Add folder…": "Ordner hinzufügen…",
|
||||||
@@ -541,6 +559,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Agent",
|
"Agent": "Agent",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Agenten-Chats können in diesen Ordnern und allem darin arbeiten — nirgendwo sonst auf diesem Computer.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Agenten-Chats können in diesen Ordnern und allem darin arbeiten — nirgendwo sonst auf diesem Computer.",
|
||||||
"Agent: %(why)s": "Agent: %(why)s",
|
"Agent: %(why)s": "Agent: %(why)s",
|
||||||
|
"All files": "Alle Dateien",
|
||||||
"Allow": "Erlauben",
|
"Allow": "Erlauben",
|
||||||
"Always": "Immer",
|
"Always": "Immer",
|
||||||
"Always allow": "Immer erlauben",
|
"Always allow": "Immer erlauben",
|
||||||
@@ -564,10 +583,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Hintergrundaufgaben",
|
"Background tasks": "Hintergrundaufgaben",
|
||||||
"Cancel": "Abbrechen",
|
"Cancel": "Abbrechen",
|
||||||
"Carry it out": "Ausführen",
|
"Carry it out": "Ausführen",
|
||||||
|
"Changed": "Geändert",
|
||||||
"Chat": "Chat",
|
"Chat": "Chat",
|
||||||
"Chats": "Chats",
|
"Chats": "Chats",
|
||||||
"Check for updates…": "Nach Updates suchen…",
|
"Check for updates…": "Nach Updates suchen…",
|
||||||
"Choose": "Auswählen",
|
"Choose": "Auswählen",
|
||||||
|
"Choose a file to see it here.": "Wählen Sie eine Datei, um sie hier zu sehen.",
|
||||||
"Choose a folder first.": "Wählen Sie zuerst einen Ordner.",
|
"Choose a folder first.": "Wählen Sie zuerst einen Ordner.",
|
||||||
"Choose a folder…": "Ordner wählen…",
|
"Choose a folder…": "Ordner wählen…",
|
||||||
"Choose a model to choose its effort.": "Wählen Sie ein Modell, um seinen Aufwand zu wählen.",
|
"Choose a model to choose its effort.": "Wählen Sie ein Modell, um seinen Aufwand zu wählen.",
|
||||||
@@ -588,6 +609,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Verbunden.",
|
"Connected.": "Verbunden.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Verbunden. Diese Shell ist auch in einem anderen Tab offen, und beide teilen sich eine Größe.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Verbunden. Diese Shell ist auch in einem anderen Tab offen, und beide teilen sich eine Größe.",
|
||||||
"Connecting…": "Verbinde…",
|
"Connecting…": "Verbinde…",
|
||||||
|
"Contents": "Inhalt",
|
||||||
"Context": "Kontext",
|
"Context": "Kontext",
|
||||||
"Context and background tasks": "Kontext und Hintergrundaufgaben",
|
"Context and background tasks": "Kontext und Hintergrundaufgaben",
|
||||||
"Context used": "Genutzter Kontext",
|
"Context used": "Genutzter Kontext",
|
||||||
@@ -609,6 +631,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "Diesen Chat löschen?",
|
"Delete this chat?": "Diesen Chat löschen?",
|
||||||
"Delete this scheduled task?": "Diese geplante Aufgabe löschen?",
|
"Delete this scheduled task?": "Diese geplante Aufgabe löschen?",
|
||||||
"Deny": "Ablehnen",
|
"Deny": "Ablehnen",
|
||||||
|
"Details": "Details",
|
||||||
"Dictate a message": "Nachricht diktieren",
|
"Dictate a message": "Nachricht diktieren",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Getrennt. Schließen und erneut öffnen, um neu zu verbinden.",
|
"Disconnected. Close and reopen to reconnect.": "Getrennt. Schließen und erneut öffnen, um neu zu verbinden.",
|
||||||
"Done": "Fertig",
|
"Done": "Fertig",
|
||||||
@@ -642,10 +665,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Ordnername",
|
"Folder name": "Ordnername",
|
||||||
"Folder on %(device)s": "Ordner auf %(device)s",
|
"Folder on %(device)s": "Ordner auf %(device)s",
|
||||||
"Folder on the computer": "Ordner auf dem Computer",
|
"Folder on the computer": "Ordner auf dem Computer",
|
||||||
|
"Folders": "Ordner",
|
||||||
"Free space": "Freier Platz",
|
"Free space": "Freier Platz",
|
||||||
"Hello": "Hallo",
|
"Hello": "Hallo",
|
||||||
"Hello, %(name)s": "Hallo, %(name)s",
|
"Hello, %(name)s": "Hallo, %(name)s",
|
||||||
"History": "Verlauf",
|
"History": "Verlauf",
|
||||||
|
"Home": "Start",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Wie viele gleichzeitig daran arbeiten, der eigene Agent des Laufs mitgezählt, mit demselben Modell.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Wie viele gleichzeitig daran arbeiten, der eigene Agent des Laufs mitgezählt, mit demselben Modell.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "In Ihren eigenen Worten. Der Server macht daraus eine Regel und eine Anweisung und zeigt Ihnen die nächsten Läufe, bevor etwas gespeichert wird.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "In Ihren eigenen Worten. Der Server macht daraus eine Regel und eine Anweisung und zeigt Ihnen die nächsten Läufe, bevor etwas gespeichert wird.",
|
||||||
"Install LLeMbas %(version)s…": "LLeMbas %(version)s installieren…",
|
"Install LLeMbas %(version)s…": "LLeMbas %(version)s installieren…",
|
||||||
@@ -684,10 +709,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modelle",
|
"Models": "Modelle",
|
||||||
"Models for scheduled tasks": "Modelle für geplante Aufgaben",
|
"Models for scheduled tasks": "Modelle für geplante Aufgaben",
|
||||||
"More": "Mehr",
|
"More": "Mehr",
|
||||||
|
"More for %(name)s": "Mehr für %(name)s",
|
||||||
"Move to folder…": "In Ordner verschieben…",
|
"Move to folder…": "In Ordner verschieben…",
|
||||||
"Move to the trash": "In den Papierkorb",
|
"Move to the trash": "In den Papierkorb",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "„%(name)s“ in den Papierkorb verschieben? Von dort lässt es sich wiederherstellen.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "„%(name)s“ in den Papierkorb verschieben? Von dort lässt es sich wiederherstellen.",
|
||||||
"Moved to the trash.": "In den Papierkorb verschoben.",
|
"Moved to the trash.": "In den Papierkorb verschoben.",
|
||||||
|
"Name": "Name",
|
||||||
"Never": "Nie",
|
"Never": "Nie",
|
||||||
"New agent chat": "Neuer Agenten-Chat",
|
"New agent chat": "Neuer Agenten-Chat",
|
||||||
"New chat": "Neuer Chat",
|
"New chat": "Neuer Chat",
|
||||||
@@ -712,6 +739,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Von einem Modell notiert",
|
"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 here yet. Not even a hobbit hole.": "Hier ist noch nichts. Nicht einmal eine Hobbithöhle.",
|
||||||
"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 remembered yet.": "Noch nichts behalten.",
|
||||||
@@ -737,6 +765,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "Oder eine Antwort eingeben",
|
"Or type an answer": "Oder eine Antwort eingeben",
|
||||||
"Outputs": "Ausgaben",
|
"Outputs": "Ausgaben",
|
||||||
"Page": "Seite",
|
"Page": "Seite",
|
||||||
|
"Path": "Pfad",
|
||||||
"Pause": "Pausieren",
|
"Pause": "Pausieren",
|
||||||
"Pause %(title)s": "%(title)s pausieren",
|
"Pause %(title)s": "%(title)s pausieren",
|
||||||
"Paused": "Pausiert",
|
"Paused": "Pausiert",
|
||||||
@@ -803,6 +832,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Angemeldet als %(who)s.",
|
"Signed in as %(who)s.": "Angemeldet als %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Größe",
|
||||||
"Skills": "Fertigkeiten",
|
"Skills": "Fertigkeiten",
|
||||||
"Skipped": "Ausgelassen",
|
"Skipped": "Ausgelassen",
|
||||||
"Something to remember…": "Etwas, das behalten werden soll…",
|
"Something to remember…": "Etwas, das behalten werden soll…",
|
||||||
@@ -899,6 +929,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Werkzeuge",
|
"Tools": "Werkzeuge",
|
||||||
"Tools and skills…": "Werkzeuge und Fähigkeiten…",
|
"Tools and skills…": "Werkzeuge und Fähigkeiten…",
|
||||||
"Try again": "Erneut versuchen",
|
"Try again": "Erneut versuchen",
|
||||||
|
"Type": "Typ",
|
||||||
"Type a command or a chat's title": "Befehl oder Chat-Titel eingeben",
|
"Type a command or a chat's title": "Befehl oder Chat-Titel eingeben",
|
||||||
"Type a folder…": "Ordner eingeben…",
|
"Type a folder…": "Ordner eingeben…",
|
||||||
"Unpin chat": "Chat loslösen",
|
"Unpin chat": "Chat loslösen",
|
||||||
@@ -920,6 +951,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Wann",
|
"When": "Wann",
|
||||||
"When it matters": "Wenn es zählt",
|
"When it matters": "Wenn es zählt",
|
||||||
"Where it runs": "Wo es läuft",
|
"Where it runs": "Wo es läuft",
|
||||||
|
"Where you are": "Wo Sie sind",
|
||||||
"Wide": "Breit",
|
"Wide": "Breit",
|
||||||
"Writing": "Schreibe",
|
"Writing": "Schreibe",
|
||||||
"Writing down…": "Wird aufgeschrieben…",
|
"Writing down…": "Wird aufgeschrieben…",
|
||||||
@@ -955,6 +987,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "fehlgeschlagen",
|
"failed": "fehlgeschlagen",
|
||||||
"findings: %(n)s": "Erkenntnisse: %(n)s",
|
"findings: %(n)s": "Erkenntnisse: %(n)s",
|
||||||
"finished": "fertig",
|
"finished": "fertig",
|
||||||
|
"folder": "Ordner",
|
||||||
"free": "frei",
|
"free": "frei",
|
||||||
"idle": "untätig",
|
"idle": "untätig",
|
||||||
"it serves one request at a time and is answering another": "es bedient eine Anfrage auf einmal und beantwortet gerade eine andere",
|
"it serves one request at a time and is answering another": "es bedient eine Anfrage auf einmal und beantwortet gerade eine andere",
|
||||||
@@ -962,6 +995,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "verspätet",
|
"late": "verspätet",
|
||||||
"light": "hell",
|
"light": "hell",
|
||||||
"messages: %(n)s": "Nachrichten: %(n)s",
|
"messages: %(n)s": "Nachrichten: %(n)s",
|
||||||
|
"mount": "Einhängung",
|
||||||
"not linked": "nicht verbunden",
|
"not linked": "nicht verbunden",
|
||||||
"not linked now": "gerade nicht verbunden",
|
"not linked now": "gerade nicht verbunden",
|
||||||
"off": "aus",
|
"off": "aus",
|
||||||
@@ -1021,6 +1055,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Una tarea sin modelo propio se ejecuta en el primero libre de estos, en este orden. Ninguno marcado: todos.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Una tarea sin modelo propio se ejecuta en el primero libre de estos, en este orden. Ninguno marcado: todos.",
|
||||||
"About": "Acerca de",
|
"About": "Acerca de",
|
||||||
"Account": "Cuenta",
|
"Account": "Cuenta",
|
||||||
|
"Actions": "Acciones",
|
||||||
"Activity": "Actividad",
|
"Activity": "Actividad",
|
||||||
"Add": "Añadir",
|
"Add": "Añadir",
|
||||||
"Add folder…": "Añadir carpeta…",
|
"Add folder…": "Añadir carpeta…",
|
||||||
@@ -1028,6 +1063,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Agente",
|
"Agent": "Agente",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Los chats de agente pueden trabajar en estas carpetas y en todo lo que contienen — en ningún otro lugar de este equipo.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Los chats de agente pueden trabajar en estas carpetas y en todo lo que contienen — en ningún otro lugar de este equipo.",
|
||||||
"Agent: %(why)s": "Agente: %(why)s",
|
"Agent: %(why)s": "Agente: %(why)s",
|
||||||
|
"All files": "Todos los archivos",
|
||||||
"Allow": "Permitir",
|
"Allow": "Permitir",
|
||||||
"Always": "Siempre",
|
"Always": "Siempre",
|
||||||
"Always allow": "Permitir siempre",
|
"Always allow": "Permitir siempre",
|
||||||
@@ -1051,10 +1087,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Tareas en segundo plano",
|
"Background tasks": "Tareas en segundo plano",
|
||||||
"Cancel": "Cancelar",
|
"Cancel": "Cancelar",
|
||||||
"Carry it out": "Llevarlo a cabo",
|
"Carry it out": "Llevarlo a cabo",
|
||||||
|
"Changed": "Cambiado",
|
||||||
"Chat": "Chat",
|
"Chat": "Chat",
|
||||||
"Chats": "Chats",
|
"Chats": "Chats",
|
||||||
"Check for updates…": "Buscar actualizaciones…",
|
"Check for updates…": "Buscar actualizaciones…",
|
||||||
"Choose": "Elegir",
|
"Choose": "Elegir",
|
||||||
|
"Choose a file to see it here.": "Elige un archivo para verlo aquí.",
|
||||||
"Choose a folder first.": "Elija primero una carpeta.",
|
"Choose a folder first.": "Elija primero una carpeta.",
|
||||||
"Choose a folder…": "Elegir una carpeta…",
|
"Choose a folder…": "Elegir una carpeta…",
|
||||||
"Choose a model to choose its effort.": "Elija un modelo para elegir su esfuerzo.",
|
"Choose a model to choose its effort.": "Elija un modelo para elegir su esfuerzo.",
|
||||||
@@ -1075,6 +1113,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Conectado.",
|
"Connected.": "Conectado.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Conectado. Este shell también está abierto en otra pestaña, y comparten tamaño.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Conectado. Este shell también está abierto en otra pestaña, y comparten tamaño.",
|
||||||
"Connecting…": "Conectando…",
|
"Connecting…": "Conectando…",
|
||||||
|
"Contents": "Contenido",
|
||||||
"Context": "Contexto",
|
"Context": "Contexto",
|
||||||
"Context and background tasks": "Contexto y tareas en segundo plano",
|
"Context and background tasks": "Contexto y tareas en segundo plano",
|
||||||
"Context used": "Contexto usado",
|
"Context used": "Contexto usado",
|
||||||
@@ -1096,6 +1135,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "¿Eliminar este chat?",
|
"Delete this chat?": "¿Eliminar este chat?",
|
||||||
"Delete this scheduled task?": "¿Eliminar esta tarea programada?",
|
"Delete this scheduled task?": "¿Eliminar esta tarea programada?",
|
||||||
"Deny": "Denegar",
|
"Deny": "Denegar",
|
||||||
|
"Details": "Detalles",
|
||||||
"Dictate a message": "Dictar un mensaje",
|
"Dictate a message": "Dictar un mensaje",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Desconectado. Cierre y vuelva a abrir para reconectar.",
|
"Disconnected. Close and reopen to reconnect.": "Desconectado. Cierre y vuelva a abrir para reconectar.",
|
||||||
"Done": "Hecho",
|
"Done": "Hecho",
|
||||||
@@ -1129,10 +1169,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Nombre de la carpeta",
|
"Folder name": "Nombre de la carpeta",
|
||||||
"Folder on %(device)s": "Carpeta en %(device)s",
|
"Folder on %(device)s": "Carpeta en %(device)s",
|
||||||
"Folder on the computer": "Carpeta en el ordenador",
|
"Folder on the computer": "Carpeta en el ordenador",
|
||||||
|
"Folders": "Carpetas",
|
||||||
"Free space": "Espacio libre",
|
"Free space": "Espacio libre",
|
||||||
"Hello": "Hola",
|
"Hello": "Hola",
|
||||||
"Hello, %(name)s": "Hola, %(name)s",
|
"Hello, %(name)s": "Hola, %(name)s",
|
||||||
"History": "Historial",
|
"History": "Historial",
|
||||||
|
"Home": "Inicio",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Cuántos trabajan en ella a la vez, contando el propio agente de la ejecución, con el mismo modelo.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Cuántos trabajan en ella a la vez, contando el propio agente de la ejecución, con el mismo modelo.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Con sus propias palabras. El servidor las convierte en una regla y una instrucción, y le muestra las próximas ejecuciones antes de guardar nada.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Con sus propias palabras. El servidor las convierte en una regla y una instrucción, y le muestra las próximas ejecuciones antes de guardar nada.",
|
||||||
"Install LLeMbas %(version)s…": "Instalar LLeMbas %(version)s…",
|
"Install LLeMbas %(version)s…": "Instalar LLeMbas %(version)s…",
|
||||||
@@ -1171,10 +1213,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modelos",
|
"Models": "Modelos",
|
||||||
"Models for scheduled tasks": "Modelos para tareas programadas",
|
"Models for scheduled tasks": "Modelos para tareas programadas",
|
||||||
"More": "Más",
|
"More": "Más",
|
||||||
|
"More for %(name)s": "Más para %(name)s",
|
||||||
"Move to folder…": "Mover a una carpeta…",
|
"Move to folder…": "Mover a una carpeta…",
|
||||||
"Move to the trash": "Mover a la papelera",
|
"Move to the trash": "Mover a la papelera",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "¿Mover «%(name)s» a la papelera? Se puede restaurar desde allí.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "¿Mover «%(name)s» a la papelera? Se puede restaurar desde allí.",
|
||||||
"Moved to the trash.": "Movido a la papelera.",
|
"Moved to the trash.": "Movido a la papelera.",
|
||||||
|
"Name": "Nombre",
|
||||||
"Never": "Nunca",
|
"Never": "Nunca",
|
||||||
"New agent chat": "Nuevo chat de agente",
|
"New agent chat": "Nuevo chat de agente",
|
||||||
"New chat": "Nuevo chat",
|
"New chat": "Nuevo chat",
|
||||||
@@ -1199,6 +1243,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Anotado por un modelo",
|
"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 here yet. Not even a hobbit hole.": "Aquí aún no hay nada. Ni siquiera un agujero hobbit.",
|
||||||
"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 remembered yet.": "Todavía no se recuerda nada.",
|
||||||
@@ -1224,6 +1269,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "O escriba una respuesta",
|
"Or type an answer": "O escriba una respuesta",
|
||||||
"Outputs": "Resultados",
|
"Outputs": "Resultados",
|
||||||
"Page": "Página",
|
"Page": "Página",
|
||||||
|
"Path": "Ruta",
|
||||||
"Pause": "Pausar",
|
"Pause": "Pausar",
|
||||||
"Pause %(title)s": "Pausar %(title)s",
|
"Pause %(title)s": "Pausar %(title)s",
|
||||||
"Paused": "En pausa",
|
"Paused": "En pausa",
|
||||||
@@ -1290,6 +1336,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Sesión iniciada como %(who)s.",
|
"Signed in as %(who)s.": "Sesión iniciada como %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Tamaño",
|
||||||
"Skills": "Habilidades",
|
"Skills": "Habilidades",
|
||||||
"Skipped": "Omitida",
|
"Skipped": "Omitida",
|
||||||
"Something to remember…": "Algo que recordar…",
|
"Something to remember…": "Algo que recordar…",
|
||||||
@@ -1386,6 +1433,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Herramientas",
|
"Tools": "Herramientas",
|
||||||
"Tools and skills…": "Herramientas y habilidades…",
|
"Tools and skills…": "Herramientas y habilidades…",
|
||||||
"Try again": "Reintentar",
|
"Try again": "Reintentar",
|
||||||
|
"Type": "Tipo",
|
||||||
"Type a command or a chat's title": "Escriba un comando o el título de un chat",
|
"Type a command or a chat's title": "Escriba un comando o el título de un chat",
|
||||||
"Type a folder…": "Escribir una carpeta…",
|
"Type a folder…": "Escribir una carpeta…",
|
||||||
"Unpin chat": "Dejar de fijar el chat",
|
"Unpin chat": "Dejar de fijar el chat",
|
||||||
@@ -1407,6 +1455,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Cuándo",
|
"When": "Cuándo",
|
||||||
"When it matters": "Cuando importa",
|
"When it matters": "Cuando importa",
|
||||||
"Where it runs": "Dónde se ejecuta",
|
"Where it runs": "Dónde se ejecuta",
|
||||||
|
"Where you are": "Dónde está",
|
||||||
"Wide": "Ancha",
|
"Wide": "Ancha",
|
||||||
"Writing": "Escribiendo",
|
"Writing": "Escribiendo",
|
||||||
"Writing down…": "Transcribiendo…",
|
"Writing down…": "Transcribiendo…",
|
||||||
@@ -1442,6 +1491,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "falló",
|
"failed": "falló",
|
||||||
"findings: %(n)s": "hallazgos: %(n)s",
|
"findings: %(n)s": "hallazgos: %(n)s",
|
||||||
"finished": "terminado",
|
"finished": "terminado",
|
||||||
|
"folder": "carpeta",
|
||||||
"free": "libre",
|
"free": "libre",
|
||||||
"idle": "inactivo",
|
"idle": "inactivo",
|
||||||
"it serves one request at a time and is answering another": "atiende una petición a la vez y está respondiendo otra",
|
"it serves one request at a time and is answering another": "atiende una petición a la vez y está respondiendo otra",
|
||||||
@@ -1449,6 +1499,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "con retraso",
|
"late": "con retraso",
|
||||||
"light": "claro",
|
"light": "claro",
|
||||||
"messages: %(n)s": "mensajes: %(n)s",
|
"messages: %(n)s": "mensajes: %(n)s",
|
||||||
|
"mount": "montaje",
|
||||||
"not linked": "no vinculado",
|
"not linked": "no vinculado",
|
||||||
"not linked now": "no vinculado ahora",
|
"not linked now": "no vinculado ahora",
|
||||||
"off": "desactivado",
|
"off": "desactivado",
|
||||||
@@ -1508,6 +1559,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Une tâche sans modèle à elle tourne sur le premier libre de ceux-ci, dans cet ordre. Aucun coché : tous.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Une tâche sans modèle à elle tourne sur le premier libre de ceux-ci, dans cet ordre. Aucun coché : tous.",
|
||||||
"About": "À propos",
|
"About": "À propos",
|
||||||
"Account": "Compte",
|
"Account": "Compte",
|
||||||
|
"Actions": "Actions",
|
||||||
"Activity": "Activité",
|
"Activity": "Activité",
|
||||||
"Add": "Ajouter",
|
"Add": "Ajouter",
|
||||||
"Add folder…": "Ajouter un dossier…",
|
"Add folder…": "Ajouter un dossier…",
|
||||||
@@ -1515,6 +1567,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Agent",
|
"Agent": "Agent",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Les discussions d'agent peuvent travailler dans ces dossiers et tout ce qu'ils contiennent — nulle part ailleurs sur cet ordinateur.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Les discussions d'agent peuvent travailler dans ces dossiers et tout ce qu'ils contiennent — nulle part ailleurs sur cet ordinateur.",
|
||||||
"Agent: %(why)s": "Agent : %(why)s",
|
"Agent: %(why)s": "Agent : %(why)s",
|
||||||
|
"All files": "Tous les fichiers",
|
||||||
"Allow": "Autoriser",
|
"Allow": "Autoriser",
|
||||||
"Always": "Toujours",
|
"Always": "Toujours",
|
||||||
"Always allow": "Toujours autoriser",
|
"Always allow": "Toujours autoriser",
|
||||||
@@ -1538,10 +1591,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Tâches en arrière-plan",
|
"Background tasks": "Tâches en arrière-plan",
|
||||||
"Cancel": "Annuler",
|
"Cancel": "Annuler",
|
||||||
"Carry it out": "Exécuter",
|
"Carry it out": "Exécuter",
|
||||||
|
"Changed": "Modifié",
|
||||||
"Chat": "Conversation",
|
"Chat": "Conversation",
|
||||||
"Chats": "Conversations",
|
"Chats": "Conversations",
|
||||||
"Check for updates…": "Rechercher des mises à jour…",
|
"Check for updates…": "Rechercher des mises à jour…",
|
||||||
"Choose": "Choisir",
|
"Choose": "Choisir",
|
||||||
|
"Choose a file to see it here.": "Choisissez un fichier pour le voir ici.",
|
||||||
"Choose a folder first.": "Choisissez d’abord un dossier.",
|
"Choose a folder first.": "Choisissez d’abord un dossier.",
|
||||||
"Choose a folder…": "Choisir un dossier…",
|
"Choose a folder…": "Choisir un dossier…",
|
||||||
"Choose a model to choose its effort.": "Choisissez un modèle pour choisir son effort.",
|
"Choose a model to choose its effort.": "Choisissez un modèle pour choisir son effort.",
|
||||||
@@ -1562,6 +1617,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Connecté.",
|
"Connected.": "Connecté.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Connecté. Ce shell est aussi ouvert dans un autre onglet, et ils partagent une taille.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Connecté. Ce shell est aussi ouvert dans un autre onglet, et ils partagent une taille.",
|
||||||
"Connecting…": "Connexion…",
|
"Connecting…": "Connexion…",
|
||||||
|
"Contents": "Contenu",
|
||||||
"Context": "Contexte",
|
"Context": "Contexte",
|
||||||
"Context and background tasks": "Contexte et tâches en arrière-plan",
|
"Context and background tasks": "Contexte et tâches en arrière-plan",
|
||||||
"Context used": "Contexte utilisé",
|
"Context used": "Contexte utilisé",
|
||||||
@@ -1583,6 +1639,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "Supprimer cette conversation ?",
|
"Delete this chat?": "Supprimer cette conversation ?",
|
||||||
"Delete this scheduled task?": "Supprimer cette tâche planifiée ?",
|
"Delete this scheduled task?": "Supprimer cette tâche planifiée ?",
|
||||||
"Deny": "Refuser",
|
"Deny": "Refuser",
|
||||||
|
"Details": "Détails",
|
||||||
"Dictate a message": "Dicter un message",
|
"Dictate a message": "Dicter un message",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Déconnecté. Fermez et rouvrez pour vous reconnecter.",
|
"Disconnected. Close and reopen to reconnect.": "Déconnecté. Fermez et rouvrez pour vous reconnecter.",
|
||||||
"Done": "Terminé",
|
"Done": "Terminé",
|
||||||
@@ -1616,10 +1673,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Nom du dossier",
|
"Folder name": "Nom du dossier",
|
||||||
"Folder on %(device)s": "Dossier sur %(device)s",
|
"Folder on %(device)s": "Dossier sur %(device)s",
|
||||||
"Folder on the computer": "Dossier sur l’ordinateur",
|
"Folder on the computer": "Dossier sur l’ordinateur",
|
||||||
|
"Folders": "Dossiers",
|
||||||
"Free space": "Espace libre",
|
"Free space": "Espace libre",
|
||||||
"Hello": "Bonjour",
|
"Hello": "Bonjour",
|
||||||
"Hello, %(name)s": "Bonjour, %(name)s",
|
"Hello, %(name)s": "Bonjour, %(name)s",
|
||||||
"History": "Historique",
|
"History": "Historique",
|
||||||
|
"Home": "Accueil",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Combien y travaillent à la fois, l’agent de l’exécution compris, avec le même modèle.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Combien y travaillent à la fois, l’agent de l’exécution compris, avec le même modèle.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Avec vos propres mots. Le serveur en fait une règle et une instruction, et vous montre les prochaines exécutions avant que rien ne soit enregistré.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Avec vos propres mots. Le serveur en fait une règle et une instruction, et vous montre les prochaines exécutions avant que rien ne soit enregistré.",
|
||||||
"Install LLeMbas %(version)s…": "Installer LLeMbas %(version)s…",
|
"Install LLeMbas %(version)s…": "Installer LLeMbas %(version)s…",
|
||||||
@@ -1658,10 +1717,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modèles",
|
"Models": "Modèles",
|
||||||
"Models for scheduled tasks": "Modèles pour les tâches planifiées",
|
"Models for scheduled tasks": "Modèles pour les tâches planifiées",
|
||||||
"More": "Plus",
|
"More": "Plus",
|
||||||
|
"More for %(name)s": "Plus pour %(name)s",
|
||||||
"Move to folder…": "Déplacer dans un dossier…",
|
"Move to folder…": "Déplacer dans un dossier…",
|
||||||
"Move to the trash": "Placer dans la corbeille",
|
"Move to the trash": "Placer dans la corbeille",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "Placer « %(name)s » dans la corbeille ? On peut l'y restaurer.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "Placer « %(name)s » dans la corbeille ? On peut l'y restaurer.",
|
||||||
"Moved to the trash.": "Placé dans la corbeille.",
|
"Moved to the trash.": "Placé dans la corbeille.",
|
||||||
|
"Name": "Nom",
|
||||||
"Never": "Jamais",
|
"Never": "Jamais",
|
||||||
"New agent chat": "Nouvelle discussion d'agent",
|
"New agent chat": "Nouvelle discussion d'agent",
|
||||||
"New chat": "Nouvelle conversation",
|
"New chat": "Nouvelle conversation",
|
||||||
@@ -1686,6 +1747,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Noté par un modèle",
|
"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 here yet. Not even a hobbit hole.": "Rien ici pour l'instant. Pas même un trou de hobbit.",
|
||||||
"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 remembered yet.": "Rien n’est encore retenu.",
|
||||||
@@ -1711,6 +1773,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "Ou saisissez une réponse",
|
"Or type an answer": "Ou saisissez une réponse",
|
||||||
"Outputs": "Sorties",
|
"Outputs": "Sorties",
|
||||||
"Page": "Page",
|
"Page": "Page",
|
||||||
|
"Path": "Chemin",
|
||||||
"Pause": "Mettre en pause",
|
"Pause": "Mettre en pause",
|
||||||
"Pause %(title)s": "Mettre %(title)s en pause",
|
"Pause %(title)s": "Mettre %(title)s en pause",
|
||||||
"Paused": "En pause",
|
"Paused": "En pause",
|
||||||
@@ -1777,6 +1840,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Connecté en tant que %(who)s.",
|
"Signed in as %(who)s.": "Connecté en tant que %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Taille",
|
||||||
"Skills": "Compétences",
|
"Skills": "Compétences",
|
||||||
"Skipped": "Ignorée",
|
"Skipped": "Ignorée",
|
||||||
"Something to remember…": "Quelque chose à retenir…",
|
"Something to remember…": "Quelque chose à retenir…",
|
||||||
@@ -1873,6 +1937,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Outils",
|
"Tools": "Outils",
|
||||||
"Tools and skills…": "Outils et compétences…",
|
"Tools and skills…": "Outils et compétences…",
|
||||||
"Try again": "Réessayer",
|
"Try again": "Réessayer",
|
||||||
|
"Type": "Type",
|
||||||
"Type a command or a chat's title": "Tapez une commande ou le titre d'une conversation",
|
"Type a command or a chat's title": "Tapez une commande ou le titre d'une conversation",
|
||||||
"Type a folder…": "Saisir un dossier…",
|
"Type a folder…": "Saisir un dossier…",
|
||||||
"Unpin chat": "Désépingler la discussion",
|
"Unpin chat": "Désépingler la discussion",
|
||||||
@@ -1894,6 +1959,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Quand",
|
"When": "Quand",
|
||||||
"When it matters": "Quand c'est important",
|
"When it matters": "Quand c'est important",
|
||||||
"Where it runs": "Où elle s’exécute",
|
"Where it runs": "Où elle s’exécute",
|
||||||
|
"Where you are": "Où vous êtes",
|
||||||
"Wide": "Large",
|
"Wide": "Large",
|
||||||
"Writing": "Écriture",
|
"Writing": "Écriture",
|
||||||
"Writing down…": "Transcription…",
|
"Writing down…": "Transcription…",
|
||||||
@@ -1929,6 +1995,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "échoué",
|
"failed": "échoué",
|
||||||
"findings: %(n)s": "constats : %(n)s",
|
"findings: %(n)s": "constats : %(n)s",
|
||||||
"finished": "terminé",
|
"finished": "terminé",
|
||||||
|
"folder": "dossier",
|
||||||
"free": "libre",
|
"free": "libre",
|
||||||
"idle": "inactif",
|
"idle": "inactif",
|
||||||
"it serves one request at a time and is answering another": "il traite une requête à la fois et en traite une autre",
|
"it serves one request at a time and is answering another": "il traite une requête à la fois et en traite une autre",
|
||||||
@@ -1936,6 +2003,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "en retard",
|
"late": "en retard",
|
||||||
"light": "clair",
|
"light": "clair",
|
||||||
"messages: %(n)s": "messages : %(n)s",
|
"messages: %(n)s": "messages : %(n)s",
|
||||||
|
"mount": "montage",
|
||||||
"not linked": "non relié",
|
"not linked": "non relié",
|
||||||
"not linked now": "non relié pour l’instant",
|
"not linked now": "non relié pour l’instant",
|
||||||
"off": "désactivé",
|
"off": "désactivé",
|
||||||
@@ -1995,6 +2063,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "A saját modell nélküli feladat ezek közül az első szabadon fut, ebben a sorrendben. Ha egy sincs bejelölve: mindegyiken.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "A saját modell nélküli feladat ezek közül az első szabadon fut, ebben a sorrendben. Ha egy sincs bejelölve: mindegyiken.",
|
||||||
"About": "Névjegy",
|
"About": "Névjegy",
|
||||||
"Account": "Fiók",
|
"Account": "Fiók",
|
||||||
|
"Actions": "Műveletek",
|
||||||
"Activity": "Tevékenység",
|
"Activity": "Tevékenység",
|
||||||
"Add": "Hozzáadás",
|
"Add": "Hozzáadás",
|
||||||
"Add folder…": "Mappa hozzáadása…",
|
"Add folder…": "Mappa hozzáadása…",
|
||||||
@@ -2002,6 +2071,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Ügynök",
|
"Agent": "Ügynök",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Az ügynökcsevegések ezekben a mappákban és mindenben, ami bennük van, dolgozhatnak — sehol máshol ezen a számítógépen.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Az ügynökcsevegések ezekben a mappákban és mindenben, ami bennük van, dolgozhatnak — sehol máshol ezen a számítógépen.",
|
||||||
"Agent: %(why)s": "Ügynök: %(why)s",
|
"Agent: %(why)s": "Ügynök: %(why)s",
|
||||||
|
"All files": "Minden fájl",
|
||||||
"Allow": "Engedélyezés",
|
"Allow": "Engedélyezés",
|
||||||
"Always": "Mindig",
|
"Always": "Mindig",
|
||||||
"Always allow": "Mindig engedélyez",
|
"Always allow": "Mindig engedélyez",
|
||||||
@@ -2025,10 +2095,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Háttérfeladatok",
|
"Background tasks": "Háttérfeladatok",
|
||||||
"Cancel": "Mégse",
|
"Cancel": "Mégse",
|
||||||
"Carry it out": "Végrehajtás",
|
"Carry it out": "Végrehajtás",
|
||||||
|
"Changed": "Módosítva",
|
||||||
"Chat": "Csevegés",
|
"Chat": "Csevegés",
|
||||||
"Chats": "Csevegések",
|
"Chats": "Csevegések",
|
||||||
"Check for updates…": "Frissítések keresése…",
|
"Check for updates…": "Frissítések keresése…",
|
||||||
"Choose": "Kiválasztás",
|
"Choose": "Kiválasztás",
|
||||||
|
"Choose a file to see it here.": "Válasszon egy fájlt, hogy itt lássa.",
|
||||||
"Choose a folder first.": "Először válasszon mappát.",
|
"Choose a folder first.": "Először válasszon mappát.",
|
||||||
"Choose a folder…": "Mappa kiválasztása…",
|
"Choose a folder…": "Mappa kiválasztása…",
|
||||||
"Choose a model to choose its effort.": "Válasszon modellt az erőfeszítés megadásához.",
|
"Choose a model to choose its effort.": "Válasszon modellt az erőfeszítés megadásához.",
|
||||||
@@ -2049,6 +2121,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Kapcsolódva.",
|
"Connected.": "Kapcsolódva.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Kapcsolódva. Ez a shell egy másik lapon is nyitva van, és közös a méretük.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Kapcsolódva. Ez a shell egy másik lapon is nyitva van, és közös a méretük.",
|
||||||
"Connecting…": "Kapcsolódás…",
|
"Connecting…": "Kapcsolódás…",
|
||||||
|
"Contents": "Tartalom",
|
||||||
"Context": "Kontextus",
|
"Context": "Kontextus",
|
||||||
"Context and background tasks": "Kontextus és háttérfeladatok",
|
"Context and background tasks": "Kontextus és háttérfeladatok",
|
||||||
"Context used": "Felhasznált kontextus",
|
"Context used": "Felhasznált kontextus",
|
||||||
@@ -2070,6 +2143,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "Törli ezt a csevegést?",
|
"Delete this chat?": "Törli ezt a csevegést?",
|
||||||
"Delete this scheduled task?": "Törli ezt az ütemezett feladatot?",
|
"Delete this scheduled task?": "Törli ezt az ütemezett feladatot?",
|
||||||
"Deny": "Elutasítás",
|
"Deny": "Elutasítás",
|
||||||
|
"Details": "Részletek",
|
||||||
"Dictate a message": "Üzenet diktálása",
|
"Dictate a message": "Üzenet diktálása",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Kapcsolat bontva. Az újrakapcsolódáshoz zárja be és nyissa meg újra.",
|
"Disconnected. Close and reopen to reconnect.": "Kapcsolat bontva. Az újrakapcsolódáshoz zárja be és nyissa meg újra.",
|
||||||
"Done": "Kész",
|
"Done": "Kész",
|
||||||
@@ -2103,10 +2177,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Mappa neve",
|
"Folder name": "Mappa neve",
|
||||||
"Folder on %(device)s": "Mappa ezen: %(device)s",
|
"Folder on %(device)s": "Mappa ezen: %(device)s",
|
||||||
"Folder on the computer": "Mappa a számítógépen",
|
"Folder on the computer": "Mappa a számítógépen",
|
||||||
|
"Folders": "Mappák",
|
||||||
"Free space": "Szabad hely",
|
"Free space": "Szabad hely",
|
||||||
"Hello": "Üdv",
|
"Hello": "Üdv",
|
||||||
"Hello, %(name)s": "Üdv, %(name)s",
|
"Hello, %(name)s": "Üdv, %(name)s",
|
||||||
"History": "Előzmények",
|
"History": "Előzmények",
|
||||||
|
"Home": "Kezdőlap",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Hányan dolgoznak rajta egyszerre, a futás saját ügynökét is beleértve, ugyanazon a modellen.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Hányan dolgoznak rajta egyszerre, a futás saját ügynökét is beleértve, ugyanazon a modellen.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "A saját szavaival. A szerver szabályt és utasítást készít belőlük, és mentés előtt megmutatja a következő futásokat.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "A saját szavaival. A szerver szabályt és utasítást készít belőlük, és mentés előtt megmutatja a következő futásokat.",
|
||||||
"Install LLeMbas %(version)s…": "LLeMbas %(version)s telepítése…",
|
"Install LLeMbas %(version)s…": "LLeMbas %(version)s telepítése…",
|
||||||
@@ -2145,10 +2221,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modellek",
|
"Models": "Modellek",
|
||||||
"Models for scheduled tasks": "Modellek az ütemezett feladatokhoz",
|
"Models for scheduled tasks": "Modellek az ütemezett feladatokhoz",
|
||||||
"More": "Több",
|
"More": "Több",
|
||||||
|
"More for %(name)s": "Továbbiak: %(name)s",
|
||||||
"Move to folder…": "Áthelyezés mappába…",
|
"Move to folder…": "Áthelyezés mappába…",
|
||||||
"Move to the trash": "Áthelyezés a lomtárba",
|
"Move to the trash": "Áthelyezés a lomtárba",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "Áthelyezi a lomtárba: „%(name)s”? Onnan visszaállítható.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "Áthelyezi a lomtárba: „%(name)s”? Onnan visszaállítható.",
|
||||||
"Moved to the trash.": "A lomtárba került.",
|
"Moved to the trash.": "A lomtárba került.",
|
||||||
|
"Name": "Név",
|
||||||
"Never": "Soha",
|
"Never": "Soha",
|
||||||
"New agent chat": "Új ügynökcsevegés",
|
"New agent chat": "Új ügynökcsevegés",
|
||||||
"New chat": "Új csevegés",
|
"New chat": "Új csevegés",
|
||||||
@@ -2173,6 +2251,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Egy modell jegyezte fel",
|
"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 here yet. Not even a hobbit hole.": "Itt még nincs semmi. Még egy hobbitlyuk sem.",
|
||||||
"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 remembered yet.": "Még nincs megjegyezve semmi.",
|
||||||
@@ -2198,6 +2277,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "Vagy írjon választ",
|
"Or type an answer": "Vagy írjon választ",
|
||||||
"Outputs": "Kimenetek",
|
"Outputs": "Kimenetek",
|
||||||
"Page": "Oldal",
|
"Page": "Oldal",
|
||||||
|
"Path": "Útvonal",
|
||||||
"Pause": "Szüneteltetés",
|
"Pause": "Szüneteltetés",
|
||||||
"Pause %(title)s": "%(title)s szüneteltetése",
|
"Pause %(title)s": "%(title)s szüneteltetése",
|
||||||
"Paused": "Szüneteltetve",
|
"Paused": "Szüneteltetve",
|
||||||
@@ -2264,6 +2344,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Bejelentkezve: %(who)s.",
|
"Signed in as %(who)s.": "Bejelentkezve: %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Méret",
|
||||||
"Skills": "Készségek",
|
"Skills": "Készségek",
|
||||||
"Skipped": "Kihagyva",
|
"Skipped": "Kihagyva",
|
||||||
"Something to remember…": "Valami, amit meg kell jegyezni…",
|
"Something to remember…": "Valami, amit meg kell jegyezni…",
|
||||||
@@ -2360,6 +2441,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Eszközök",
|
"Tools": "Eszközök",
|
||||||
"Tools and skills…": "Eszközök és készségek…",
|
"Tools and skills…": "Eszközök és készségek…",
|
||||||
"Try again": "Újrapróbálás",
|
"Try again": "Újrapróbálás",
|
||||||
|
"Type": "Típus",
|
||||||
"Type a command or a chat's title": "Írjon be egy parancsot vagy egy csevegés címét",
|
"Type a command or a chat's title": "Írjon be egy parancsot vagy egy csevegés címét",
|
||||||
"Type a folder…": "Mappa megadása…",
|
"Type a folder…": "Mappa megadása…",
|
||||||
"Unpin chat": "Kitűzés megszüntetése",
|
"Unpin chat": "Kitűzés megszüntetése",
|
||||||
@@ -2381,6 +2463,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Mikor",
|
"When": "Mikor",
|
||||||
"When it matters": "Ha számít",
|
"When it matters": "Ha számít",
|
||||||
"Where it runs": "Hol fut",
|
"Where it runs": "Hol fut",
|
||||||
|
"Where you are": "Hol van",
|
||||||
"Wide": "Széles",
|
"Wide": "Széles",
|
||||||
"Writing": "Írás",
|
"Writing": "Írás",
|
||||||
"Writing down…": "Leírás…",
|
"Writing down…": "Leírás…",
|
||||||
@@ -2416,6 +2499,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "sikertelen",
|
"failed": "sikertelen",
|
||||||
"findings: %(n)s": "megállapítások: %(n)s",
|
"findings: %(n)s": "megállapítások: %(n)s",
|
||||||
"finished": "kész",
|
"finished": "kész",
|
||||||
|
"folder": "mappa",
|
||||||
"free": "szabad",
|
"free": "szabad",
|
||||||
"idle": "tétlen",
|
"idle": "tétlen",
|
||||||
"it serves one request at a time and is answering another": "egyszerre egy kérést szolgál ki, és éppen egy másikat válaszol meg",
|
"it serves one request at a time and is answering another": "egyszerre egy kérést szolgál ki, és éppen egy másikat válaszol meg",
|
||||||
@@ -2423,6 +2507,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "késve",
|
"late": "késve",
|
||||||
"light": "világos",
|
"light": "világos",
|
||||||
"messages: %(n)s": "üzenetek: %(n)s",
|
"messages: %(n)s": "üzenetek: %(n)s",
|
||||||
|
"mount": "csatolás",
|
||||||
"not linked": "nincs összekapcsolva",
|
"not linked": "nincs összekapcsolva",
|
||||||
"not linked now": "most nincs összekapcsolva",
|
"not linked now": "most nincs összekapcsolva",
|
||||||
"off": "ki",
|
"off": "ki",
|
||||||
@@ -2482,6 +2567,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Un’attività senza un modello proprio gira sul primo libero di questi, in quest’ordine. Nessuno spuntato: tutti.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Un’attività senza un modello proprio gira sul primo libero di questi, in quest’ordine. Nessuno spuntato: tutti.",
|
||||||
"About": "Informazioni",
|
"About": "Informazioni",
|
||||||
"Account": "Account",
|
"Account": "Account",
|
||||||
|
"Actions": "Azioni",
|
||||||
"Activity": "Attività",
|
"Activity": "Attività",
|
||||||
"Add": "Aggiungi",
|
"Add": "Aggiungi",
|
||||||
"Add folder…": "Aggiungi cartella…",
|
"Add folder…": "Aggiungi cartella…",
|
||||||
@@ -2489,6 +2575,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Agente",
|
"Agent": "Agente",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Le chat con agente possono lavorare in queste cartelle e in tutto ciò che contengono — in nessun altro punto di questo computer.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Le chat con agente possono lavorare in queste cartelle e in tutto ciò che contengono — in nessun altro punto di questo computer.",
|
||||||
"Agent: %(why)s": "Agente: %(why)s",
|
"Agent: %(why)s": "Agente: %(why)s",
|
||||||
|
"All files": "Tutti i file",
|
||||||
"Allow": "Consenti",
|
"Allow": "Consenti",
|
||||||
"Always": "Sempre",
|
"Always": "Sempre",
|
||||||
"Always allow": "Consenti sempre",
|
"Always allow": "Consenti sempre",
|
||||||
@@ -2512,10 +2599,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Attività in background",
|
"Background tasks": "Attività in background",
|
||||||
"Cancel": "Annulla",
|
"Cancel": "Annulla",
|
||||||
"Carry it out": "Eseguilo",
|
"Carry it out": "Eseguilo",
|
||||||
|
"Changed": "Modificato",
|
||||||
"Chat": "Chat",
|
"Chat": "Chat",
|
||||||
"Chats": "Chat",
|
"Chats": "Chat",
|
||||||
"Check for updates…": "Cerca aggiornamenti…",
|
"Check for updates…": "Cerca aggiornamenti…",
|
||||||
"Choose": "Scegli",
|
"Choose": "Scegli",
|
||||||
|
"Choose a file to see it here.": "Scegli un file per vederlo qui.",
|
||||||
"Choose a folder first.": "Scegli prima una cartella.",
|
"Choose a folder first.": "Scegli prima una cartella.",
|
||||||
"Choose a folder…": "Scegli una cartella…",
|
"Choose a folder…": "Scegli una cartella…",
|
||||||
"Choose a model to choose its effort.": "Scegli un modello per sceglierne l’impegno.",
|
"Choose a model to choose its effort.": "Scegli un modello per sceglierne l’impegno.",
|
||||||
@@ -2536,6 +2625,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Connesso.",
|
"Connected.": "Connesso.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Connesso. Questa shell è aperta anche in un'altra scheda, e le due condividono la dimensione.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Connesso. Questa shell è aperta anche in un'altra scheda, e le due condividono la dimensione.",
|
||||||
"Connecting…": "Connessione in corso…",
|
"Connecting…": "Connessione in corso…",
|
||||||
|
"Contents": "Contenuto",
|
||||||
"Context": "Contesto",
|
"Context": "Contesto",
|
||||||
"Context and background tasks": "Contesto e attività in background",
|
"Context and background tasks": "Contesto e attività in background",
|
||||||
"Context used": "Contesto usato",
|
"Context used": "Contesto usato",
|
||||||
@@ -2557,6 +2647,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "Eliminare questa chat?",
|
"Delete this chat?": "Eliminare questa chat?",
|
||||||
"Delete this scheduled task?": "Eliminare questa attività pianificata?",
|
"Delete this scheduled task?": "Eliminare questa attività pianificata?",
|
||||||
"Deny": "Nega",
|
"Deny": "Nega",
|
||||||
|
"Details": "Dettagli",
|
||||||
"Dictate a message": "Detta un messaggio",
|
"Dictate a message": "Detta un messaggio",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Disconnesso. Chiudi e riapri per riconnetterti.",
|
"Disconnected. Close and reopen to reconnect.": "Disconnesso. Chiudi e riapri per riconnetterti.",
|
||||||
"Done": "Fatto",
|
"Done": "Fatto",
|
||||||
@@ -2590,10 +2681,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Nome della cartella",
|
"Folder name": "Nome della cartella",
|
||||||
"Folder on %(device)s": "Cartella su %(device)s",
|
"Folder on %(device)s": "Cartella su %(device)s",
|
||||||
"Folder on the computer": "Cartella sul computer",
|
"Folder on the computer": "Cartella sul computer",
|
||||||
|
"Folders": "Cartelle",
|
||||||
"Free space": "Spazio libero",
|
"Free space": "Spazio libero",
|
||||||
"Hello": "Ciao",
|
"Hello": "Ciao",
|
||||||
"Hello, %(name)s": "Ciao, %(name)s",
|
"Hello, %(name)s": "Ciao, %(name)s",
|
||||||
"History": "Cronologia",
|
"History": "Cronologia",
|
||||||
|
"Home": "Home",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Quanti ci lavorano alla volta, compreso l’agente dell’esecuzione, sullo stesso modello.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Quanti ci lavorano alla volta, compreso l’agente dell’esecuzione, sullo stesso modello.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Con parole tue. Il server ne ricava una regola e un’istruzione e ti mostra le prossime esecuzioni prima di salvare.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Con parole tue. Il server ne ricava una regola e un’istruzione e ti mostra le prossime esecuzioni prima di salvare.",
|
||||||
"Install LLeMbas %(version)s…": "Installa LLeMbas %(version)s…",
|
"Install LLeMbas %(version)s…": "Installa LLeMbas %(version)s…",
|
||||||
@@ -2632,10 +2725,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modelli",
|
"Models": "Modelli",
|
||||||
"Models for scheduled tasks": "Modelli per le attività pianificate",
|
"Models for scheduled tasks": "Modelli per le attività pianificate",
|
||||||
"More": "Altro",
|
"More": "Altro",
|
||||||
|
"More for %(name)s": "Altro per %(name)s",
|
||||||
"Move to folder…": "Sposta in una cartella…",
|
"Move to folder…": "Sposta in una cartella…",
|
||||||
"Move to the trash": "Sposta nel cestino",
|
"Move to the trash": "Sposta nel cestino",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "Spostare «%(name)s» nel cestino? Si può ripristinare da lì.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "Spostare «%(name)s» nel cestino? Si può ripristinare da lì.",
|
||||||
"Moved to the trash.": "Spostato nel cestino.",
|
"Moved to the trash.": "Spostato nel cestino.",
|
||||||
|
"Name": "Nome",
|
||||||
"Never": "Mai",
|
"Never": "Mai",
|
||||||
"New agent chat": "Nuova chat con agente",
|
"New agent chat": "Nuova chat con agente",
|
||||||
"New chat": "Nuova chat",
|
"New chat": "Nuova chat",
|
||||||
@@ -2660,6 +2755,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Annotato da un modello",
|
"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 here yet. Not even a hobbit hole.": "Ancora niente qui. Nemmeno una caverna hobbit.",
|
||||||
"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 remembered yet.": "Ancora niente da ricordare.",
|
||||||
@@ -2685,6 +2781,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "Oppure scriva una risposta",
|
"Or type an answer": "Oppure scriva una risposta",
|
||||||
"Outputs": "Risultati",
|
"Outputs": "Risultati",
|
||||||
"Page": "Pagina",
|
"Page": "Pagina",
|
||||||
|
"Path": "Percorso",
|
||||||
"Pause": "Metti in pausa",
|
"Pause": "Metti in pausa",
|
||||||
"Pause %(title)s": "Metti in pausa %(title)s",
|
"Pause %(title)s": "Metti in pausa %(title)s",
|
||||||
"Paused": "In pausa",
|
"Paused": "In pausa",
|
||||||
@@ -2751,6 +2848,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Accesso effettuato come %(who)s.",
|
"Signed in as %(who)s.": "Accesso effettuato come %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Dimensione",
|
||||||
"Skills": "Abilità",
|
"Skills": "Abilità",
|
||||||
"Skipped": "Saltata",
|
"Skipped": "Saltata",
|
||||||
"Something to remember…": "Qualcosa da ricordare…",
|
"Something to remember…": "Qualcosa da ricordare…",
|
||||||
@@ -2847,6 +2945,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Strumenti",
|
"Tools": "Strumenti",
|
||||||
"Tools and skills…": "Strumenti e abilità…",
|
"Tools and skills…": "Strumenti e abilità…",
|
||||||
"Try again": "Riprova",
|
"Try again": "Riprova",
|
||||||
|
"Type": "Tipo",
|
||||||
"Type a command or a chat's title": "Digiti un comando o il titolo di una chat",
|
"Type a command or a chat's title": "Digiti un comando o il titolo di una chat",
|
||||||
"Type a folder…": "Digita una cartella…",
|
"Type a folder…": "Digita una cartella…",
|
||||||
"Unpin chat": "Togli la chat dalle fissate",
|
"Unpin chat": "Togli la chat dalle fissate",
|
||||||
@@ -2868,6 +2967,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Quando",
|
"When": "Quando",
|
||||||
"When it matters": "Quando conta",
|
"When it matters": "Quando conta",
|
||||||
"Where it runs": "Dove viene eseguito",
|
"Where it runs": "Dove viene eseguito",
|
||||||
|
"Where you are": "Dove sei",
|
||||||
"Wide": "Larga",
|
"Wide": "Larga",
|
||||||
"Writing": "Scrittura",
|
"Writing": "Scrittura",
|
||||||
"Writing down…": "Trascrizione…",
|
"Writing down…": "Trascrizione…",
|
||||||
@@ -2903,6 +3003,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "fallito",
|
"failed": "fallito",
|
||||||
"findings: %(n)s": "risultati: %(n)s",
|
"findings: %(n)s": "risultati: %(n)s",
|
||||||
"finished": "finito",
|
"finished": "finito",
|
||||||
|
"folder": "cartella",
|
||||||
"free": "libero",
|
"free": "libero",
|
||||||
"idle": "inattivo",
|
"idle": "inattivo",
|
||||||
"it serves one request at a time and is answering another": "serve una richiesta alla volta e ne sta servendo un’altra",
|
"it serves one request at a time and is answering another": "serve una richiesta alla volta e ne sta servendo un’altra",
|
||||||
@@ -2910,6 +3011,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "in ritardo",
|
"late": "in ritardo",
|
||||||
"light": "chiaro",
|
"light": "chiaro",
|
||||||
"messages: %(n)s": "messaggi: %(n)s",
|
"messages: %(n)s": "messaggi: %(n)s",
|
||||||
|
"mount": "montaggio",
|
||||||
"not linked": "non collegato",
|
"not linked": "non collegato",
|
||||||
"not linked now": "non collegato ora",
|
"not linked now": "non collegato ora",
|
||||||
"off": "disattivato",
|
"off": "disattivato",
|
||||||
@@ -2969,6 +3071,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Zadanie bez własnego modelu działa na pierwszym wolnym z tych, w tej kolejności. Żaden niezaznaczony: wszystkie.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Zadanie bez własnego modelu działa na pierwszym wolnym z tych, w tej kolejności. Żaden niezaznaczony: wszystkie.",
|
||||||
"About": "O aplikacji",
|
"About": "O aplikacji",
|
||||||
"Account": "Konto",
|
"Account": "Konto",
|
||||||
|
"Actions": "Działania",
|
||||||
"Activity": "Aktywność",
|
"Activity": "Aktywność",
|
||||||
"Add": "Dodaj",
|
"Add": "Dodaj",
|
||||||
"Add folder…": "Dodaj folder…",
|
"Add folder…": "Dodaj folder…",
|
||||||
@@ -2976,6 +3079,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Agent",
|
"Agent": "Agent",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Czaty z agentem mogą pracować w tych folderach i we wszystkim, co zawierają — nigdzie indziej na tym komputerze.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Czaty z agentem mogą pracować w tych folderach i we wszystkim, co zawierają — nigdzie indziej na tym komputerze.",
|
||||||
"Agent: %(why)s": "Agent: %(why)s",
|
"Agent: %(why)s": "Agent: %(why)s",
|
||||||
|
"All files": "Wszystkie pliki",
|
||||||
"Allow": "Pozwól",
|
"Allow": "Pozwól",
|
||||||
"Always": "Zawsze",
|
"Always": "Zawsze",
|
||||||
"Always allow": "Zawsze zezwalaj",
|
"Always allow": "Zawsze zezwalaj",
|
||||||
@@ -2999,10 +3103,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Zadania w tle",
|
"Background tasks": "Zadania w tle",
|
||||||
"Cancel": "Anuluj",
|
"Cancel": "Anuluj",
|
||||||
"Carry it out": "Wykonaj",
|
"Carry it out": "Wykonaj",
|
||||||
|
"Changed": "Zmieniono",
|
||||||
"Chat": "Czat",
|
"Chat": "Czat",
|
||||||
"Chats": "Czaty",
|
"Chats": "Czaty",
|
||||||
"Check for updates…": "Sprawdź aktualizacje…",
|
"Check for updates…": "Sprawdź aktualizacje…",
|
||||||
"Choose": "Wybierz",
|
"Choose": "Wybierz",
|
||||||
|
"Choose a file to see it here.": "Wybierz plik, aby zobaczyć go tutaj.",
|
||||||
"Choose a folder first.": "Najpierw wybierz folder.",
|
"Choose a folder first.": "Najpierw wybierz folder.",
|
||||||
"Choose a folder…": "Wybierz folder…",
|
"Choose a folder…": "Wybierz folder…",
|
||||||
"Choose a model to choose its effort.": "Wybierz model, aby wybrać jego wysiłek.",
|
"Choose a model to choose its effort.": "Wybierz model, aby wybrać jego wysiłek.",
|
||||||
@@ -3023,6 +3129,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Połączono.",
|
"Connected.": "Połączono.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Połączono. Ta powłoka jest otwarta też w innej karcie i dzielą one rozmiar.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Połączono. Ta powłoka jest otwarta też w innej karcie i dzielą one rozmiar.",
|
||||||
"Connecting…": "Łączenie…",
|
"Connecting…": "Łączenie…",
|
||||||
|
"Contents": "Zawartość",
|
||||||
"Context": "Kontekst",
|
"Context": "Kontekst",
|
||||||
"Context and background tasks": "Kontekst i zadania w tle",
|
"Context and background tasks": "Kontekst i zadania w tle",
|
||||||
"Context used": "Użyty kontekst",
|
"Context used": "Użyty kontekst",
|
||||||
@@ -3044,6 +3151,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "Usunąć ten czat?",
|
"Delete this chat?": "Usunąć ten czat?",
|
||||||
"Delete this scheduled task?": "Usunąć to zaplanowane zadanie?",
|
"Delete this scheduled task?": "Usunąć to zaplanowane zadanie?",
|
||||||
"Deny": "Odrzuć",
|
"Deny": "Odrzuć",
|
||||||
|
"Details": "Szczegóły",
|
||||||
"Dictate a message": "Podyktuj wiadomość",
|
"Dictate a message": "Podyktuj wiadomość",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Rozłączono. Aby połączyć ponownie, zamknij i otwórz ponownie.",
|
"Disconnected. Close and reopen to reconnect.": "Rozłączono. Aby połączyć ponownie, zamknij i otwórz ponownie.",
|
||||||
"Done": "Gotowe",
|
"Done": "Gotowe",
|
||||||
@@ -3077,10 +3185,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Nazwa folderu",
|
"Folder name": "Nazwa folderu",
|
||||||
"Folder on %(device)s": "Folder na %(device)s",
|
"Folder on %(device)s": "Folder na %(device)s",
|
||||||
"Folder on the computer": "Folder na komputerze",
|
"Folder on the computer": "Folder na komputerze",
|
||||||
|
"Folders": "Foldery",
|
||||||
"Free space": "Wolne miejsce",
|
"Free space": "Wolne miejsce",
|
||||||
"Hello": "Cześć",
|
"Hello": "Cześć",
|
||||||
"Hello, %(name)s": "Cześć, %(name)s",
|
"Hello, %(name)s": "Cześć, %(name)s",
|
||||||
"History": "Historia",
|
"History": "Historia",
|
||||||
|
"Home": "Start",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Ilu pracuje nad nim naraz, łącznie z własnym agentem uruchomienia, na tym samym modelu.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Ilu pracuje nad nim naraz, łącznie z własnym agentem uruchomienia, na tym samym modelu.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Własnymi słowami. Serwer zamieni je w regułę i polecenie i pokaże następne uruchomienia, zanim cokolwiek zostanie zapisane.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Własnymi słowami. Serwer zamieni je w regułę i polecenie i pokaże następne uruchomienia, zanim cokolwiek zostanie zapisane.",
|
||||||
"Install LLeMbas %(version)s…": "Zainstaluj LLeMbas %(version)s…",
|
"Install LLeMbas %(version)s…": "Zainstaluj LLeMbas %(version)s…",
|
||||||
@@ -3119,10 +3229,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modele",
|
"Models": "Modele",
|
||||||
"Models for scheduled tasks": "Modele dla zaplanowanych zadań",
|
"Models for scheduled tasks": "Modele dla zaplanowanych zadań",
|
||||||
"More": "Więcej",
|
"More": "Więcej",
|
||||||
|
"More for %(name)s": "Więcej dla %(name)s",
|
||||||
"Move to folder…": "Przenieś do folderu…",
|
"Move to folder…": "Przenieś do folderu…",
|
||||||
"Move to the trash": "Przenieś do kosza",
|
"Move to the trash": "Przenieś do kosza",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "Przenieść „%(name)s” do kosza? Można go stamtąd przywrócić.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "Przenieść „%(name)s” do kosza? Można go stamtąd przywrócić.",
|
||||||
"Moved to the trash.": "Przeniesiono do kosza.",
|
"Moved to the trash.": "Przeniesiono do kosza.",
|
||||||
|
"Name": "Nazwa",
|
||||||
"Never": "Nigdy",
|
"Never": "Nigdy",
|
||||||
"New agent chat": "Nowy czat z agentem",
|
"New agent chat": "Nowy czat z agentem",
|
||||||
"New chat": "Nowy czat",
|
"New chat": "Nowy czat",
|
||||||
@@ -3147,6 +3259,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Zanotowane przez model",
|
"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 here yet. Not even a hobbit hole.": "Jeszcze nic tu nie ma. Nawet hobbiciej norki.",
|
||||||
"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 remembered yet.": "Na razie nic nie zapamiętano.",
|
||||||
@@ -3172,6 +3285,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "Albo wpisz odpowiedź",
|
"Or type an answer": "Albo wpisz odpowiedź",
|
||||||
"Outputs": "Wyniki",
|
"Outputs": "Wyniki",
|
||||||
"Page": "Strona",
|
"Page": "Strona",
|
||||||
|
"Path": "Ścieżka",
|
||||||
"Pause": "Wstrzymaj",
|
"Pause": "Wstrzymaj",
|
||||||
"Pause %(title)s": "Wstrzymaj %(title)s",
|
"Pause %(title)s": "Wstrzymaj %(title)s",
|
||||||
"Paused": "Wstrzymany",
|
"Paused": "Wstrzymany",
|
||||||
@@ -3238,6 +3352,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Zalogowano jako %(who)s.",
|
"Signed in as %(who)s.": "Zalogowano jako %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Rozmiar",
|
||||||
"Skills": "Umiejętności",
|
"Skills": "Umiejętności",
|
||||||
"Skipped": "Pominięty",
|
"Skipped": "Pominięty",
|
||||||
"Something to remember…": "Coś do zapamiętania…",
|
"Something to remember…": "Coś do zapamiętania…",
|
||||||
@@ -3334,6 +3449,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Narzędzia",
|
"Tools": "Narzędzia",
|
||||||
"Tools and skills…": "Narzędzia i umiejętności…",
|
"Tools and skills…": "Narzędzia i umiejętności…",
|
||||||
"Try again": "Spróbuj ponownie",
|
"Try again": "Spróbuj ponownie",
|
||||||
|
"Type": "Typ",
|
||||||
"Type a command or a chat's title": "Wpisz polecenie lub tytuł czatu",
|
"Type a command or a chat's title": "Wpisz polecenie lub tytuł czatu",
|
||||||
"Type a folder…": "Wpisz folder…",
|
"Type a folder…": "Wpisz folder…",
|
||||||
"Unpin chat": "Odepnij czat",
|
"Unpin chat": "Odepnij czat",
|
||||||
@@ -3355,6 +3471,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Kiedy",
|
"When": "Kiedy",
|
||||||
"When it matters": "Gdy to ważne",
|
"When it matters": "Gdy to ważne",
|
||||||
"Where it runs": "Gdzie działa",
|
"Where it runs": "Gdzie działa",
|
||||||
|
"Where you are": "Gdzie jesteś",
|
||||||
"Wide": "Szeroki",
|
"Wide": "Szeroki",
|
||||||
"Writing": "Pisanie",
|
"Writing": "Pisanie",
|
||||||
"Writing down…": "Zapisywanie…",
|
"Writing down…": "Zapisywanie…",
|
||||||
@@ -3390,6 +3507,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "nie powiodło się",
|
"failed": "nie powiodło się",
|
||||||
"findings: %(n)s": "ustalenia: %(n)s",
|
"findings: %(n)s": "ustalenia: %(n)s",
|
||||||
"finished": "zakończony",
|
"finished": "zakończony",
|
||||||
|
"folder": "folder",
|
||||||
"free": "wolny",
|
"free": "wolny",
|
||||||
"idle": "bezczynny",
|
"idle": "bezczynny",
|
||||||
"it serves one request at a time and is answering another": "obsługuje jedno żądanie naraz i właśnie obsługuje inne",
|
"it serves one request at a time and is answering another": "obsługuje jedno żądanie naraz i właśnie obsługuje inne",
|
||||||
@@ -3397,6 +3515,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "z opóźnieniem",
|
"late": "z opóźnieniem",
|
||||||
"light": "jasny",
|
"light": "jasny",
|
||||||
"messages: %(n)s": "wiadomości: %(n)s",
|
"messages: %(n)s": "wiadomości: %(n)s",
|
||||||
|
"mount": "montowanie",
|
||||||
"not linked": "niepołączony",
|
"not linked": "niepołączony",
|
||||||
"not linked now": "teraz niepołączony",
|
"not linked now": "teraz niepołączony",
|
||||||
"off": "wył.",
|
"off": "wył.",
|
||||||
@@ -3456,6 +3575,7 @@ const LEMBAS_I18N = {
|
|||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastného modelu beží na prvom voľnom z týchto, v tomto poradí. Žiadny nezaškrtnutý: všetky.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastného modelu beží na prvom voľnom z týchto, v tomto poradí. Žiadny nezaškrtnutý: všetky.",
|
||||||
"About": "O aplikácii",
|
"About": "O aplikácii",
|
||||||
"Account": "Účet",
|
"Account": "Účet",
|
||||||
|
"Actions": "Akcie",
|
||||||
"Activity": "Aktivita",
|
"Activity": "Aktivita",
|
||||||
"Add": "Pridať",
|
"Add": "Pridať",
|
||||||
"Add folder…": "Pridať priečinok…",
|
"Add folder…": "Pridať priečinok…",
|
||||||
@@ -3463,6 +3583,7 @@ const LEMBAS_I18N = {
|
|||||||
"Agent": "Agent",
|
"Agent": "Agent",
|
||||||
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Agentské konverzácie môžu pracovať v týchto priečinkoch a vo všetkom, čo obsahujú — nikde inde na tomto počítači.",
|
"Agent chats can work in these folders and anything inside them — nowhere else on this computer.": "Agentské konverzácie môžu pracovať v týchto priečinkoch a vo všetkom, čo obsahujú — nikde inde na tomto počítači.",
|
||||||
"Agent: %(why)s": "Agent: %(why)s",
|
"Agent: %(why)s": "Agent: %(why)s",
|
||||||
|
"All files": "Všetky súbory",
|
||||||
"Allow": "Povoliť",
|
"Allow": "Povoliť",
|
||||||
"Always": "Vždy",
|
"Always": "Vždy",
|
||||||
"Always allow": "Vždy povoliť",
|
"Always allow": "Vždy povoliť",
|
||||||
@@ -3486,10 +3607,12 @@ const LEMBAS_I18N = {
|
|||||||
"Background tasks": "Úlohy na pozadí",
|
"Background tasks": "Úlohy na pozadí",
|
||||||
"Cancel": "Zrušiť",
|
"Cancel": "Zrušiť",
|
||||||
"Carry it out": "Vykonať to",
|
"Carry it out": "Vykonať to",
|
||||||
|
"Changed": "Zmenené",
|
||||||
"Chat": "Konverzácia",
|
"Chat": "Konverzácia",
|
||||||
"Chats": "Konverzácie",
|
"Chats": "Konverzácie",
|
||||||
"Check for updates…": "Skontrolovať aktualizácie…",
|
"Check for updates…": "Skontrolovať aktualizácie…",
|
||||||
"Choose": "Vybrať",
|
"Choose": "Vybrať",
|
||||||
|
"Choose a file to see it here.": "Vyberte súbor a uvidíte ho tu.",
|
||||||
"Choose a folder first.": "Najprv vyberte priečinok.",
|
"Choose a folder first.": "Najprv vyberte priečinok.",
|
||||||
"Choose a folder…": "Vybrať priečinok…",
|
"Choose a folder…": "Vybrať priečinok…",
|
||||||
"Choose a model to choose its effort.": "Vyberte model, aby ste mohli zvoliť jeho náročnosť.",
|
"Choose a model to choose its effort.": "Vyberte model, aby ste mohli zvoliť jeho náročnosť.",
|
||||||
@@ -3510,6 +3633,7 @@ const LEMBAS_I18N = {
|
|||||||
"Connected.": "Pripojené.",
|
"Connected.": "Pripojené.",
|
||||||
"Connected. This shell is also open in another tab, and they share a size.": "Pripojené. Tento shell je otvorený aj v inej karte a zdieľajú veľkosť.",
|
"Connected. This shell is also open in another tab, and they share a size.": "Pripojené. Tento shell je otvorený aj v inej karte a zdieľajú veľkosť.",
|
||||||
"Connecting…": "Pripája sa…",
|
"Connecting…": "Pripája sa…",
|
||||||
|
"Contents": "Obsah",
|
||||||
"Context": "Kontext",
|
"Context": "Kontext",
|
||||||
"Context and background tasks": "Kontext a úlohy na pozadí",
|
"Context and background tasks": "Kontext a úlohy na pozadí",
|
||||||
"Context used": "Využitý kontext",
|
"Context used": "Využitý kontext",
|
||||||
@@ -3531,6 +3655,7 @@ const LEMBAS_I18N = {
|
|||||||
"Delete this chat?": "Zmazať túto konverzáciu?",
|
"Delete this chat?": "Zmazať túto konverzáciu?",
|
||||||
"Delete this scheduled task?": "Odstrániť túto naplánovanú úlohu?",
|
"Delete this scheduled task?": "Odstrániť túto naplánovanú úlohu?",
|
||||||
"Deny": "Odmietnuť",
|
"Deny": "Odmietnuť",
|
||||||
|
"Details": "Podrobnosti",
|
||||||
"Dictate a message": "Nadiktovať správu",
|
"Dictate a message": "Nadiktovať správu",
|
||||||
"Disconnected. Close and reopen to reconnect.": "Odpojené. Zavrite a znova otvorte, aby sa pripojilo.",
|
"Disconnected. Close and reopen to reconnect.": "Odpojené. Zavrite a znova otvorte, aby sa pripojilo.",
|
||||||
"Done": "Hotovo",
|
"Done": "Hotovo",
|
||||||
@@ -3564,10 +3689,12 @@ const LEMBAS_I18N = {
|
|||||||
"Folder name": "Názov zložky",
|
"Folder name": "Názov zložky",
|
||||||
"Folder on %(device)s": "Priečinok na zariadení %(device)s",
|
"Folder on %(device)s": "Priečinok na zariadení %(device)s",
|
||||||
"Folder on the computer": "Priečinok na počítači",
|
"Folder on the computer": "Priečinok na počítači",
|
||||||
|
"Folders": "Priečinky",
|
||||||
"Free space": "Voľné miesto",
|
"Free space": "Voľné miesto",
|
||||||
"Hello": "Dobrý deň",
|
"Hello": "Dobrý deň",
|
||||||
"Hello, %(name)s": "Dobrý deň, %(name)s",
|
"Hello, %(name)s": "Dobrý deň, %(name)s",
|
||||||
"History": "História",
|
"History": "História",
|
||||||
|
"Home": "Domov",
|
||||||
"How many work on it at once, the run's own agent among them, on the same model.": "Koľko na nej pracuje naraz, vrátane vlastného agenta behu, na rovnakom modeli.",
|
"How many work on it at once, the run's own agent among them, on the same model.": "Koľko na nej pracuje naraz, vrátane vlastného agenta behu, na rovnakom modeli.",
|
||||||
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Vlastnými slovami. Server z nich vytvorí pravidlo a pokyn a pred uložením vám ukáže ďalšie spustenia.",
|
"In your own words. The server reads them into a rule and an instruction, and shows you the next runs before anything is saved.": "Vlastnými slovami. Server z nich vytvorí pravidlo a pokyn a pred uložením vám ukáže ďalšie spustenia.",
|
||||||
"Install LLeMbas %(version)s…": "Nainštalovať LLeMbas %(version)s…",
|
"Install LLeMbas %(version)s…": "Nainštalovať LLeMbas %(version)s…",
|
||||||
@@ -3606,10 +3733,12 @@ const LEMBAS_I18N = {
|
|||||||
"Models": "Modely",
|
"Models": "Modely",
|
||||||
"Models for scheduled tasks": "Modely pre naplánované úlohy",
|
"Models for scheduled tasks": "Modely pre naplánované úlohy",
|
||||||
"More": "Viac",
|
"More": "Viac",
|
||||||
|
"More for %(name)s": "Ďalšie pre %(name)s",
|
||||||
"Move to folder…": "Presunúť do priečinka…",
|
"Move to folder…": "Presunúť do priečinka…",
|
||||||
"Move to the trash": "Presunúť do koša",
|
"Move to the trash": "Presunúť do koša",
|
||||||
"Move “%(name)s” to the trash? It can be restored from there.": "Presunúť „%(name)s“ do koša? Odtiaľ sa dá obnoviť.",
|
"Move “%(name)s” to the trash? It can be restored from there.": "Presunúť „%(name)s“ do koša? Odtiaľ sa dá obnoviť.",
|
||||||
"Moved to the trash.": "Presunuté do koša.",
|
"Moved to the trash.": "Presunuté do koša.",
|
||||||
|
"Name": "Názov",
|
||||||
"Never": "Nikdy",
|
"Never": "Nikdy",
|
||||||
"New agent chat": "Nová agentská konverzácia",
|
"New agent chat": "Nová agentská konverzácia",
|
||||||
"New chat": "Nová konverzácia",
|
"New chat": "Nová konverzácia",
|
||||||
@@ -3634,6 +3763,7 @@ const LEMBAS_I18N = {
|
|||||||
"Noted by a model": "Zaznamenal model",
|
"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 here yet. Not even a hobbit hole.": "Zatiaľ tu nič nie je. Ani hobitia nora.",
|
||||||
"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 remembered yet.": "Zatiaľ si nič nepamätá.",
|
||||||
@@ -3659,6 +3789,7 @@ const LEMBAS_I18N = {
|
|||||||
"Or type an answer": "Alebo napíšte odpoveď",
|
"Or type an answer": "Alebo napíšte odpoveď",
|
||||||
"Outputs": "Výstupy",
|
"Outputs": "Výstupy",
|
||||||
"Page": "Stránka",
|
"Page": "Stránka",
|
||||||
|
"Path": "Cesta",
|
||||||
"Pause": "Pozastaviť",
|
"Pause": "Pozastaviť",
|
||||||
"Pause %(title)s": "Pozastaviť %(title)s",
|
"Pause %(title)s": "Pozastaviť %(title)s",
|
||||||
"Paused": "Pozastavené",
|
"Paused": "Pozastavené",
|
||||||
@@ -3725,6 +3856,7 @@ const LEMBAS_I18N = {
|
|||||||
"Signed in as %(who)s.": "Prihlásený ako %(who)s.",
|
"Signed in as %(who)s.": "Prihlásený ako %(who)s.",
|
||||||
"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",
|
||||||
|
"Size": "Veľkosť",
|
||||||
"Skills": "Schopnosti",
|
"Skills": "Schopnosti",
|
||||||
"Skipped": "Vynechané",
|
"Skipped": "Vynechané",
|
||||||
"Something to remember…": "Niečo na zapamätanie…",
|
"Something to remember…": "Niečo na zapamätanie…",
|
||||||
@@ -3821,6 +3953,7 @@ const LEMBAS_I18N = {
|
|||||||
"Tools": "Nástroje",
|
"Tools": "Nástroje",
|
||||||
"Tools and skills…": "Nástroje a schopnosti…",
|
"Tools and skills…": "Nástroje a schopnosti…",
|
||||||
"Try again": "Skúsiť znova",
|
"Try again": "Skúsiť znova",
|
||||||
|
"Type": "Typ",
|
||||||
"Type a command or a chat's title": "Napíšte príkaz alebo názov konverzácie",
|
"Type a command or a chat's title": "Napíšte príkaz alebo názov konverzácie",
|
||||||
"Type a folder…": "Zadať priečinok…",
|
"Type a folder…": "Zadať priečinok…",
|
||||||
"Unpin chat": "Odopnúť konverzáciu",
|
"Unpin chat": "Odopnúť konverzáciu",
|
||||||
@@ -3842,6 +3975,7 @@ const LEMBAS_I18N = {
|
|||||||
"When": "Kedy",
|
"When": "Kedy",
|
||||||
"When it matters": "Keď na tom záleží",
|
"When it matters": "Keď na tom záleží",
|
||||||
"Where it runs": "Kde sa spúšťa",
|
"Where it runs": "Kde sa spúšťa",
|
||||||
|
"Where you are": "Kde ste",
|
||||||
"Wide": "Široký",
|
"Wide": "Široký",
|
||||||
"Writing": "Píše",
|
"Writing": "Píše",
|
||||||
"Writing down…": "Zapisujem…",
|
"Writing down…": "Zapisujem…",
|
||||||
@@ -3877,6 +4011,7 @@ const LEMBAS_I18N = {
|
|||||||
"failed": "zlyhalo",
|
"failed": "zlyhalo",
|
||||||
"findings: %(n)s": "zistenia: %(n)s",
|
"findings: %(n)s": "zistenia: %(n)s",
|
||||||
"finished": "dokončený",
|
"finished": "dokončený",
|
||||||
|
"folder": "priečinok",
|
||||||
"free": "voľný",
|
"free": "voľný",
|
||||||
"idle": "nečinný",
|
"idle": "nečinný",
|
||||||
"it serves one request at a time and is answering another": "vybavuje jednu požiadavku naraz a práve vybavuje inú",
|
"it serves one request at a time and is answering another": "vybavuje jednu požiadavku naraz a práve vybavuje inú",
|
||||||
@@ -3884,6 +4019,7 @@ const LEMBAS_I18N = {
|
|||||||
"late": "s oneskorením",
|
"late": "s oneskorením",
|
||||||
"light": "svetlá",
|
"light": "svetlá",
|
||||||
"messages: %(n)s": "správy: %(n)s",
|
"messages: %(n)s": "správy: %(n)s",
|
||||||
|
"mount": "pripojenie",
|
||||||
"not linked": "neprepojené",
|
"not linked": "neprepojené",
|
||||||
"not linked now": "teraz nie je prepojený",
|
"not linked now": "teraz nie je prepojený",
|
||||||
"off": "vypnuté",
|
"off": "vypnuté",
|
||||||
|
|||||||
+27
-13
@@ -100,22 +100,36 @@
|
|||||||
<div class="head__tools"><div class="tabs" role="tablist" id="files-tabs"></div></div>
|
<div class="head__tools"><div class="tabs" role="tablist" id="files-tabs"></div></div>
|
||||||
</header>
|
</header>
|
||||||
<div class="page__body files">
|
<div class="page__body files">
|
||||||
<section class="files__pane" id="pane-local" aria-label="This computer" data-i18n-aria-label>
|
<section class="fx" id="pane-local" aria-label="This computer" data-i18n-aria-label>
|
||||||
<div class="files__head">
|
<div class="fx__bar">
|
||||||
<div class="files__where" id="local-where"></div>
|
<div class="fx__where" id="local-where"></div>
|
||||||
<button type="button" class="tool" id="local-refresh" aria-label="Read again" title="Read again" data-i18n-aria-label data-i18n-title></button>
|
<nav class="crumbs" id="local-crumbs" aria-label="Where you are" data-i18n-aria-label></nav>
|
||||||
|
<div class="fx__tools">
|
||||||
|
<button type="button" class="tool" id="local-refresh" aria-label="Read again" title="Read again" data-i18n-aria-label data-i18n-title></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="fx__panes">
|
||||||
|
<aside class="fx__tree" id="local-tree" aria-label="Folders" data-i18n-aria-label></aside>
|
||||||
|
<div class="fx__list" id="local-list"></div>
|
||||||
|
<aside class="fx__details" id="local-details" aria-label="Details" data-i18n-aria-label></aside>
|
||||||
</div>
|
</div>
|
||||||
<div class="files__tree" id="local-tree"></div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="files__pane" id="pane-server" hidden aria-label="Server" data-i18n-aria-label>
|
<section class="fx" id="pane-server" hidden aria-label="Server" data-i18n-aria-label>
|
||||||
<div class="files__head">
|
<div class="fx__bar">
|
||||||
<div class="files__where muted" id="server-where"></div>
|
<div class="fx__where"></div>
|
||||||
<button type="button" class="tool" id="server-upload" aria-label="Upload" title="Upload" data-i18n-aria-label data-i18n-title></button>
|
<nav class="crumbs" id="server-crumbs" aria-label="Where you are" data-i18n-aria-label></nav>
|
||||||
<button type="button" class="tool" id="server-mkdir" aria-label="New folder" title="New folder" data-i18n-aria-label data-i18n-title></button>
|
<div class="fx__tools">
|
||||||
<button type="button" class="tool" id="server-refresh" aria-label="Read again" title="Read again" data-i18n-aria-label data-i18n-title></button>
|
<button type="button" class="tool" id="server-upload" aria-label="Upload" title="Upload" data-i18n-aria-label data-i18n-title></button>
|
||||||
|
<button type="button" class="tool" id="server-mkdir" aria-label="New folder" title="New folder" data-i18n-aria-label data-i18n-title></button>
|
||||||
|
<button type="button" class="tool" id="server-refresh" aria-label="Read again" title="Read again" data-i18n-aria-label data-i18n-title></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="status fx__status" id="server-status" hidden></p>
|
||||||
|
<div class="fx__panes">
|
||||||
|
<aside class="fx__tree" id="server-tree" aria-label="Folders" data-i18n-aria-label></aside>
|
||||||
|
<div class="fx__list" id="server-list"></div>
|
||||||
|
<aside class="fx__details" id="server-details" aria-label="Details" data-i18n-aria-label></aside>
|
||||||
</div>
|
</div>
|
||||||
<p class="status" id="server-status" hidden></p>
|
|
||||||
<div class="files__tree" id="server-tree"></div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ export const ICONS = {
|
|||||||
mic: ["M8 1.5a2.5 2.5 0 012.5 2.5v4a2.5 2.5 0 01-5 0V4A2.5 2.5 0 018 1.5zM8 3a1 1 0 00-1 1v4a1 1 0 002 0V4a1 1 0 00-1-1z", "M3.5 7.5H5a3 3 0 006 0h1.5a4.5 4.5 0 01-3.75 4.44V14h-1.5v-2.06A4.5 4.5 0 013.5 7.5z"],
|
mic: ["M8 1.5a2.5 2.5 0 012.5 2.5v4a2.5 2.5 0 01-5 0V4A2.5 2.5 0 018 1.5zM8 3a1 1 0 00-1 1v4a1 1 0 002 0V4a1 1 0 00-1-1z", "M3.5 7.5H5a3 3 0 006 0h1.5a4.5 4.5 0 01-3.75 4.44V14h-1.5v-2.06A4.5 4.5 0 013.5 7.5z"],
|
||||||
speaker: ["M2 6h2.5L8 3v10L4.5 10H2z", "M10.2 5.2a4 4 0 010 5.6l-1.06-1.06a2.5 2.5 0 000-3.48z", "M12.3 3.1a7 7 0 010 9.8l-1.06-1.06a5.5 5.5 0 000-7.68z"],
|
speaker: ["M2 6h2.5L8 3v10L4.5 10H2z", "M10.2 5.2a4 4 0 010 5.6l-1.06-1.06a2.5 2.5 0 000-3.48z", "M12.3 3.1a7 7 0 010 9.8l-1.06-1.06a5.5 5.5 0 000-7.68z"],
|
||||||
picture: ["M1.5 2.5h13v11h-13zM3 4v6.4l2.75-2.75 2.5 2.5 1.75-1.75L13 11.4V4z", "M10.5 5a1.25 1.25 0 110 2.5 1.25 1.25 0 010-2.5z"],
|
picture: ["M1.5 2.5h13v11h-13zM3 4v6.4l2.75-2.75 2.5 2.5 1.75-1.75L13 11.4V4z", "M10.5 5a1.25 1.25 0 110 2.5 1.25 1.25 0 010-2.5z"],
|
||||||
|
fileText: ["M3 1.5h6.5L13 5v9.5H3zM4.5 3v10h7V6H8.5V3z", "M6 8h4v1H6zM6 10h4v1H6z"],
|
||||||
|
fileCode: ["M3 1.5h6.5L13 5v9.5H3zM4.5 3v10h7V6H8.5V3z", "M6.7 7.6l.7.7-1.2 1.2 1.2 1.2-.7.7-1.9-1.9z", "M9.3 7.6l1.9 1.9-1.9 1.9-.7-.7 1.2-1.2-1.2-1.2z"],
|
||||||
|
drive: ["M1.5 4h13v8h-13zM3 5.5v5h10v-5z", "M10.5 7.25h1.5v1.5h-1.5z"],
|
||||||
info: ["M8 1.5a6.5 6.5 0 110 13 6.5 6.5 0 010-13zM8 3a5 5 0 100 10A5 5 0 008 3z", "M7.25 7h1.5v4.5h-1.5zM7.25 4.5h1.5V6h-1.5z"],
|
info: ["M8 1.5a6.5 6.5 0 110 13 6.5 6.5 0 010-13zM8 3a5 5 0 100 10A5 5 0 008 3z", "M7.25 7h1.5v4.5h-1.5zM7.25 4.5h1.5V6h-1.5z"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+434
-140
@@ -1,141 +1,376 @@
|
|||||||
// The two explorers of the Files page, kept apart as two tabs: this computer's -- the agent's
|
// The Files page: two explorers, kept apart as two tabs -- this computer's (the agent's folders,
|
||||||
// folders, read through a command that reaches only them (src-tauri/src/files.rs) -- and the
|
// read through a command that reaches only them, src-tauri/src/files.rs) and the server's (the
|
||||||
// server's tree, for the chats, read through /api/v2. A file opens in the viewer either way.
|
// person's files, as the chats see them, read through /api/v2). Each is the web explorer's shape,
|
||||||
|
// using the window's width: where you are (crumbs) and what can be done there, the folder tree,
|
||||||
|
// the list (name, type, size, changed), and the chosen file's details and preview. A file opens in
|
||||||
|
// the viewer either way.
|
||||||
|
//
|
||||||
|
// Nothing here is ever an empty box: a directory being read says Loading…, past SLOW_MS it says
|
||||||
|
// the server has not answered (with Try again, the late answer still drawn), and a refusal, an
|
||||||
|
// answer that is not a listing or a failure to draw it is said in its place with Try again.
|
||||||
|
|
||||||
import { $, h, clear, icon, ICONS, bytes } from "./dom.js"
|
import { $, h, clear, icon, ICONS, bytes } from "./dom.js"
|
||||||
import { invoke, get, post, del, why } from "./bridge.js"
|
import { invoke, get, post, del, why } from "./bridge.js"
|
||||||
import { menu, ask } from "./popup.js"
|
import { menu, ask } from "./popup.js"
|
||||||
import { openViewer } from "./viewer.js"
|
import { openViewer, showKind, withheldWords } from "./viewer.js"
|
||||||
|
|
||||||
// The server's tree as the app's door gives it, read as the person (their own access, no chat):
|
// The server's tree as the app's door gives it, read as the person (their own access, no chat):
|
||||||
// GET /files?path=/a -> { path, parent, entries: [{ name, path, kind: "dir"|"file"|"mount", size, mime, updated_at }] }
|
// GET /files?path=/a -> { path, parent, entries: [{ name, path, kind: "dir"|"file"|"mount", size, mime, updated_at }] }
|
||||||
// GET /files/content?path= -> { path, name, size, mime, kind, language, sha, text, withheld?: { reason, message } }
|
// GET /files/content?path= -> { path, name, size, mime, kind, language, sha, text, withheld?: { reason, message } }
|
||||||
// A path the person cannot read is the server's 404, in its words.
|
// A path the person cannot read is the server's 404, in its words. `/` answers with where the
|
||||||
|
// person's explorer opens (`fs.start`): `/` itself for an organisation's owner or administrator,
|
||||||
|
// the person's home for anyone else -- its `path` says which.
|
||||||
export const SERVER_FILES = {
|
export const SERVER_FILES = {
|
||||||
list: (path) => `/files?path=${encodeURIComponent(path || "/")}`,
|
list: (path) => `/files?path=${encodeURIComponent(path || "/")}`,
|
||||||
read: (path) => `/files/content?path=${encodeURIComponent(path)}`,
|
read: (path) => `/files/content?path=${encodeURIComponent(path)}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A directory in either tree: the server's mounts open like its folders. */
|
|
||||||
const isDir = (e) => e.kind === "dir" || e.kind === "mount"
|
|
||||||
|
|
||||||
/** How long a directory may take before the pane says it is still waiting, and offers to ask again. */
|
/** How long a directory may take before the pane says it is still waiting, and offers to ask again. */
|
||||||
export const SLOW_MS = 8000
|
export const SLOW_MS = 8000
|
||||||
|
|
||||||
/**
|
/** How long an answer is shared between the tree and the list after it came. */
|
||||||
* A lazily opened tree in `box`. `list(path)` -> entries; `open(entry)` for a file; `actions(entry)`
|
const SHARED_MS = 1500
|
||||||
* (optional) -> menu items for an entry's more button. Returns `reload(path)`, which reads a
|
|
||||||
* directory that is open again (the root by `rootPath`), the folders open inside it kept open.
|
/** The most of a file the details pane previews: the viewer has the rest. */
|
||||||
*
|
const PREVIEW_CHARS = 6000
|
||||||
* A directory is never an empty box: while it is read it says "Loading…" (and, past SLOW_MS, that
|
|
||||||
* the answer has not come, with Try again); a refusal, or anything that breaks drawing it, is said
|
/** A directory in either tree: the server's mounts open like its folders. */
|
||||||
* in its place with Try again. Only the latest reading of a directory is drawn: an answer that
|
const isDir = (e) => e.kind === "dir" || e.kind === "mount"
|
||||||
* comes after the directory was read again, closed or redrawn is dropped, not appended.
|
|
||||||
*/
|
const IMAGE = /\.(png|jpe?g|gif|webp|bmp|ico|avif|svg)$/i
|
||||||
function tree(box, { list, open, rootPath, actions }) {
|
|
||||||
clear(box)
|
/** An entry's icon, by what the viewer would make of it (the web's `file_icon`). */
|
||||||
const ul = h("ul", { class: "tree", role: "tree" })
|
function iconOf(e) {
|
||||||
box.append(ul)
|
if (e.kind === "mount") return ICONS.drive
|
||||||
const levels = new Map() // path -> { ul, depth }, each directory drawn now
|
if (isDir(e)) return ICONS.folder
|
||||||
const opened = new Set() // the directories open now
|
if ((e.mime || "").startsWith("image/") || IMAGE.test(e.name)) return ICONS.picture
|
||||||
const asked = new WeakMap() // ul -> the reading it shows now (a number)
|
const { kind } = showKind(e.path || e.name)
|
||||||
const level = async (parent, path, depth) => {
|
return kind === "code" || kind === "html" ? ICONS.fileCode : kind === "markdown" || kind === "text" ? ICONS.fileText : ICONS.file
|
||||||
levels.set(path, { ul: parent, depth })
|
}
|
||||||
const mine = (asked.get(parent) || 0) + 1
|
|
||||||
asked.set(parent, mine)
|
/** The Type column: a folder, a mount, or the extension (the web's `type_label`). */
|
||||||
const stale = () => asked.get(parent) !== mine || !parent.isConnected
|
function typeOf(e) {
|
||||||
const again = () => {
|
if (e.kind === "mount") return t("mount")
|
||||||
clear(parent)
|
if (isDir(e)) return t("folder")
|
||||||
return level(parent, path, depth)
|
const i = e.name.lastIndexOf(".")
|
||||||
}
|
return i > 0 ? e.name.slice(i + 1).toLowerCase() : ""
|
||||||
const retry = () => h("button", { type: "button", class: "btn btn--sm tree__retry", text: t("Try again"), onclick: again })
|
}
|
||||||
const loading = h("li", { class: "tree__note is-loading", role: "status", "aria-live": "polite" }, h("span", { text: t("Loading…") }))
|
|
||||||
loading.style.setProperty("--depth", String(depth))
|
/** A stamp as a date the person reads, and the whole of it for a title. */
|
||||||
parent.append(loading)
|
function dateOf(stamp) {
|
||||||
const slow = setTimeout(() => {
|
if (!stamp) return { short: "", full: "" }
|
||||||
if (stale()) return
|
const d = new Date(stamp)
|
||||||
loading.append(h("span", { text: ` ${t("The server has not answered in %(n)s seconds.", { n: Math.round(SLOW_MS / 1000) })}` }), retry())
|
if (Number.isNaN(d.getTime())) return { short: "", full: "" }
|
||||||
}, SLOW_MS)
|
const lang = document.documentElement.lang || undefined
|
||||||
const said = (text) => {
|
return {
|
||||||
const note = h("li", { class: "tree__note is-error", role: "alert" }, h("span", { text }), retry())
|
short: new Intl.DateTimeFormat(lang, { day: "numeric", month: "short", year: "numeric" }).format(d),
|
||||||
note.style.setProperty("--depth", String(depth))
|
full: new Intl.DateTimeFormat(lang, { dateStyle: "long", timeStyle: "short" }).format(d),
|
||||||
return note
|
|
||||||
}
|
|
||||||
let entries
|
|
||||||
try {
|
|
||||||
entries = await list(path)
|
|
||||||
} catch (err) {
|
|
||||||
clearTimeout(slow)
|
|
||||||
if (!stale()) loading.replaceWith(said(why(err)))
|
|
||||||
return
|
|
||||||
} finally {
|
|
||||||
clearTimeout(slow)
|
|
||||||
}
|
|
||||||
if (stale()) return
|
|
||||||
try {
|
|
||||||
if (!Array.isArray(entries)) throw { detail: t("The server's answer could not be read.") }
|
|
||||||
const items = []
|
|
||||||
if (!entries.length) items.push(h("li", { class: "tree__note", text: t("Empty folder") }))
|
|
||||||
for (const e of entries) items.push(item(e, depth))
|
|
||||||
loading.replaceWith(...items)
|
|
||||||
} catch (err) {
|
|
||||||
loading.replaceWith(said(err instanceof Error ? err.message : why(err)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const item = (e, depth) => {
|
}
|
||||||
|
|
||||||
|
/** Is the details pane on the screen (wide windows): a file is chosen there, else opened. */
|
||||||
|
const detailsShown = (el) => !!el && getComputedStyle(el).display !== "none"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read something into `holder` and draw it, never leaving it empty: Loading… at once, the slow
|
||||||
|
* note and Try again past SLOW_MS, a refusal or a failure to draw in its place. `tag` is the
|
||||||
|
* element the notes are (an `li` inside a list). Only the latest reading of a holder is drawn:
|
||||||
|
* one that comes after the holder was read again, or left the page, is dropped. Returns a promise
|
||||||
|
* that settles once the reading is drawn or said.
|
||||||
|
*/
|
||||||
|
function reading(holder, { tag = "div", depth = 0, load, draw }) {
|
||||||
|
const mine = (readings.get(holder) || 0) + 1
|
||||||
|
readings.set(holder, mine)
|
||||||
|
const stale = () => readings.get(holder) !== mine || !holder.isConnected
|
||||||
|
const again = () => reading(holder, { tag, depth, load, draw })
|
||||||
|
const retry = () => h("button", { type: "button", class: "btn btn--sm fnote__retry", text: t("Try again"), onclick: again })
|
||||||
|
const note = (cls, role, text) => {
|
||||||
|
const n = h(tag, { class: `fnote ${cls}`, role }, h("span", { text }))
|
||||||
|
n.style.setProperty("--depth", String(depth))
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
clear(holder)
|
||||||
|
const loading = note("is-loading", "status", t("Loading…"))
|
||||||
|
loading.setAttribute("aria-live", "polite")
|
||||||
|
holder.append(loading)
|
||||||
|
const slow = setTimeout(() => {
|
||||||
|
if (!stale()) loading.append(h("span", { text: t("The server has not answered in %(n)s seconds.", { n: Math.round(SLOW_MS / 1000) }) }), retry())
|
||||||
|
}, SLOW_MS)
|
||||||
|
const said = (text) => {
|
||||||
|
const n = note("is-error", "alert", text)
|
||||||
|
n.append(retry())
|
||||||
|
clear(holder).append(n)
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(load)
|
||||||
|
.then(
|
||||||
|
(answer) => {
|
||||||
|
clearTimeout(slow)
|
||||||
|
if (stale()) return
|
||||||
|
try {
|
||||||
|
clear(holder)
|
||||||
|
draw(answer)
|
||||||
|
} catch (err) {
|
||||||
|
said(err instanceof Error ? err.message : why(err))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
clearTimeout(slow)
|
||||||
|
if (!stale()) said(why(err))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const readings = new WeakMap() // holder -> the reading it shows now (a number)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One explorer, inside the pane's elements (`crumbs`, `tree`, `list`, `details`). `source`:
|
||||||
|
* key "local" | "server" (the rows' data, the tests' handle)
|
||||||
|
* list(path) -> { path, entries } (`path` as answered: the server's `/` says where it is)
|
||||||
|
* top() the directory the explorer opens at (the server's: as `/` answered)
|
||||||
|
* ask(path) the path to ask for a directory (the server's top is asked as `/`)
|
||||||
|
* crumbs(path) -> [[name, path], ...] from the top down
|
||||||
|
* rootName() the tree's first line
|
||||||
|
* open(entry) in the viewer
|
||||||
|
* preview(entry) -> { text, withheld, size }
|
||||||
|
* actions(entry) (optional) -> menu items
|
||||||
|
*/
|
||||||
|
function explorer(el, source) {
|
||||||
|
let current = null // the directory listed now (as answered)
|
||||||
|
let chosen = null // the file whose details show
|
||||||
|
// One request per directory for the tree and the list together: an answer is shared while it
|
||||||
|
// is on its way and for a moment after (the tree drawn again once the top is known); a refusal
|
||||||
|
// is never kept, so Try again asks afresh. Read again and a change forget them all.
|
||||||
|
const pending = new Map() // asked path -> promise
|
||||||
|
const fetchDir = (path) => {
|
||||||
|
const asked = source.ask(path)
|
||||||
|
if (!pending.has(asked)) {
|
||||||
|
const p = Promise.resolve().then(() => source.list(asked))
|
||||||
|
pending.set(asked, p)
|
||||||
|
const forget = () => pending.get(asked) === p && pending.delete(asked)
|
||||||
|
p.then(() => setTimeout(forget, SHARED_MS), forget)
|
||||||
|
}
|
||||||
|
return pending.get(asked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- The folder tree: folders only, opened lazily; a name lists its folder.
|
||||||
|
const treeBox = el.tree
|
||||||
|
const opened = new Set()
|
||||||
|
const branches = new Map() // path -> { ul, depth }
|
||||||
|
const drawTree = () => {
|
||||||
|
clear(treeBox)
|
||||||
|
const rootRow = h(
|
||||||
|
"button",
|
||||||
|
{ type: "button", class: "ftree__row ftree__root", dataset: { path: source.top() }, onclick: () => go(source.top()) },
|
||||||
|
icon(ICONS.folder),
|
||||||
|
h("span", { class: "ftree__name", text: source.rootName() }),
|
||||||
|
)
|
||||||
|
const ul = h("ul", { class: "ftree", role: "tree", "aria-label": t("Folders") })
|
||||||
|
treeBox.append(rootRow, ul)
|
||||||
|
branch(ul, source.top(), 0)
|
||||||
|
mark()
|
||||||
|
}
|
||||||
|
const branch = (ul, path, depth) => {
|
||||||
|
branches.set(path, { ul, depth })
|
||||||
|
return reading(ul, {
|
||||||
|
tag: "li",
|
||||||
|
depth,
|
||||||
|
load: () => fetchDir(path),
|
||||||
|
draw: (answer) => {
|
||||||
|
const dirs = (answer.entries || []).filter(isDir)
|
||||||
|
for (const e of dirs) ul.append(twig(e, depth))
|
||||||
|
mark()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const twig = (e, depth) => {
|
||||||
|
const chevron = h("button", { type: "button", class: "ftree__chevron", "aria-label": e.name, "aria-expanded": "false", tabindex: "-1" }, icon(ICONS.chevron))
|
||||||
const row = h(
|
const row = h(
|
||||||
"button",
|
"button",
|
||||||
{ type: "button", class: "tree__row", title: e.path, "aria-expanded": isDir(e) ? "false" : null },
|
{ type: "button", class: "ftree__row", title: e.path, dataset: { path: e.path } },
|
||||||
h("span", { class: "tree__chevron" }, isDir(e) ? icon(ICONS.chevron) : null),
|
icon(iconOf(e)),
|
||||||
icon(isDir(e) ? ICONS.folder : ICONS.file),
|
h("span", { class: "ftree__name", text: e.name }),
|
||||||
h("span", { class: "tree__name", text: e.name }),
|
|
||||||
h("span", { class: "tree__size", text: isDir(e) ? "" : bytes(e.size) }),
|
|
||||||
)
|
)
|
||||||
row.style.setProperty("--depth", String(depth))
|
const line = h("div", { class: "ftree__line" }, chevron, row)
|
||||||
const items = actions?.(e) || []
|
line.style.setProperty("--depth", String(depth))
|
||||||
const li = h(
|
const li = h("li", { role: "treeitem", dataset: { path: e.path } }, line)
|
||||||
"li",
|
let kids = null
|
||||||
{ role: "treeitem", class: items.length ? "tree__item" : null, dataset: { path: e.path } },
|
const expand = (opening) => {
|
||||||
row,
|
chevron.setAttribute("aria-expanded", String(opening))
|
||||||
items.length
|
li.setAttribute("aria-expanded", String(opening))
|
||||||
? h("button", { type: "button", class: "tool tree__more", "aria-label": t("More"), title: t("More"), onclick: (ev) => menu(ev.currentTarget, items) }, icon(ICONS.more))
|
kids?.remove()
|
||||||
: null,
|
kids = null
|
||||||
)
|
if (!opening) {
|
||||||
if (isDir(e)) {
|
opened.delete(e.path)
|
||||||
let kids = null
|
branches.delete(e.path)
|
||||||
const expand = async (opening) => {
|
return
|
||||||
row.setAttribute("aria-expanded", String(opening))
|
|
||||||
if (!opening) {
|
|
||||||
opened.delete(e.path)
|
|
||||||
levels.delete(e.path)
|
|
||||||
kids?.remove()
|
|
||||||
kids = null
|
|
||||||
return
|
|
||||||
}
|
|
||||||
opened.add(e.path)
|
|
||||||
kids?.remove()
|
|
||||||
kids = h("ul", { role: "group" })
|
|
||||||
li.append(kids)
|
|
||||||
await level(kids, e.path, depth + 1)
|
|
||||||
}
|
}
|
||||||
row.addEventListener("click", () => expand(row.getAttribute("aria-expanded") !== "true"))
|
opened.add(e.path)
|
||||||
if (opened.has(e.path)) expand(true)
|
kids = h("ul", { role: "group" })
|
||||||
} else row.addEventListener("click", () => open(e))
|
li.append(kids)
|
||||||
|
branch(kids, e.path, depth + 1)
|
||||||
|
}
|
||||||
|
chevron.addEventListener("click", () => expand(chevron.getAttribute("aria-expanded") !== "true"))
|
||||||
|
row.addEventListener("click", () => {
|
||||||
|
go(e.path)
|
||||||
|
if (chevron.getAttribute("aria-expanded") !== "true") expand(true)
|
||||||
|
})
|
||||||
|
if (opened.has(e.path)) expand(true)
|
||||||
return li
|
return li
|
||||||
}
|
}
|
||||||
const done = level(ul, rootPath, 0)
|
/** The tree's line for the folder listed now. */
|
||||||
const reload = (path) => {
|
const mark = () => {
|
||||||
const at = levels.get(path) || levels.get(rootPath)
|
for (const r of treeBox.querySelectorAll(".ftree__row")) {
|
||||||
if (!at) return Promise.resolve()
|
const here = r.dataset.path === current || (r.classList.contains("ftree__root") && current === source.top())
|
||||||
clear(at.ul)
|
r.classList.toggle("is-current", here)
|
||||||
return level(at.ul, levels.has(path) ? path : rootPath, at.depth)
|
if (here) r.setAttribute("aria-current", "true")
|
||||||
|
else r.removeAttribute("aria-current")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { done, reload }
|
|
||||||
|
// --- Where you are.
|
||||||
|
const drawCrumbs = () => {
|
||||||
|
const parts = source.crumbs(current ?? source.top())
|
||||||
|
clear(el.crumbs).append(
|
||||||
|
...parts.flatMap(([name, at], i) => {
|
||||||
|
const last = i === parts.length - 1
|
||||||
|
const part = last
|
||||||
|
? h("span", { class: "crumbs__here", "aria-current": "page", title: at, text: name })
|
||||||
|
: h("button", { type: "button", class: "crumbs__link", title: at, text: name, onclick: () => go(at) })
|
||||||
|
return i ? [h("span", { class: "crumbs__sep", "aria-hidden": "true", text: "/" }), part] : [part]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
el.crumbs.scrollLeft = el.crumbs.scrollWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- The list.
|
||||||
|
const go = (path) => {
|
||||||
|
current = path
|
||||||
|
chosen = null
|
||||||
|
drawDetails()
|
||||||
|
drawCrumbs()
|
||||||
|
mark()
|
||||||
|
return reading(el.list, {
|
||||||
|
load: () => fetchDir(path),
|
||||||
|
draw: (answer) => {
|
||||||
|
if (!answer || !Array.isArray(answer.entries)) throw { detail: t("The server's answer could not be read.") }
|
||||||
|
if (answer.path !== undefined && answer.path !== path && source.ask(path) === source.ask(answer.path)) current = answer.path
|
||||||
|
drawCrumbs()
|
||||||
|
mark()
|
||||||
|
drawRows(answer.entries)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const drawRows = (entries) => {
|
||||||
|
if (!entries.length) {
|
||||||
|
el.list.append(h("div", { class: "fx__empty" }, icon(ICONS.folder), h("p", { text: t("Nothing here yet. Not even a hobbit hole.") })))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const head = h(
|
||||||
|
"div",
|
||||||
|
{ class: "frow frow--head", role: "row" },
|
||||||
|
h("span", { role: "columnheader", text: t("Name") }),
|
||||||
|
h("span", { role: "columnheader", class: "frow__type", text: t("Type") }),
|
||||||
|
h("span", { role: "columnheader", class: "frow__size", text: t("Size") }),
|
||||||
|
h("span", { role: "columnheader", class: "frow__when", text: t("Changed") }),
|
||||||
|
h("span", { role: "columnheader" }, h("span", { class: "visually-hidden", text: t("Actions") })),
|
||||||
|
)
|
||||||
|
el.list.append(h("div", { class: "frows", role: "table", "aria-label": t("Contents") }, head, entries.map(row)))
|
||||||
|
}
|
||||||
|
const row = (e) => {
|
||||||
|
const date = dateOf(e.updated_at)
|
||||||
|
const items = source.actions?.(e) || []
|
||||||
|
const openRow = h(
|
||||||
|
"button",
|
||||||
|
{ type: "button", class: "frow__open", title: e.path },
|
||||||
|
h("span", { class: `frow__icon frow__icon--${e.kind === "mount" ? "mount" : isDir(e) ? "dir" : showKind(e.name).kind}` }, icon(iconOf(e))),
|
||||||
|
h("span", { class: "frow__label", text: e.name }),
|
||||||
|
)
|
||||||
|
const r = h(
|
||||||
|
"div",
|
||||||
|
{ class: "frow", role: "row", dataset: { path: e.path, kind: e.kind } },
|
||||||
|
h("span", { role: "cell", class: "frow__name" }, openRow, e.kind === "mount" ? h("span", { class: "badge", text: t("mount") }) : null),
|
||||||
|
h("span", { role: "cell", class: "frow__type", text: typeOf(e) }),
|
||||||
|
h("span", { role: "cell", class: "frow__size", text: isDir(e) ? "" : bytes(e.size) }),
|
||||||
|
h("span", { role: "cell", class: "frow__when", title: date.full, text: date.short }),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ role: "cell", class: "frow__actions" },
|
||||||
|
items.length
|
||||||
|
? h("button", { type: "button", class: "tool frow__more", "aria-label": t("More for %(name)s", { name: e.name }), title: t("More for %(name)s", { name: e.name }), onclick: (ev) => menu(ev.currentTarget, items) }, icon(ICONS.more))
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
openRow.addEventListener("click", () => {
|
||||||
|
if (isDir(e)) go(e.path)
|
||||||
|
else if (detailsShown(el.details)) choose(e)
|
||||||
|
else source.open(e)
|
||||||
|
})
|
||||||
|
openRow.addEventListener("dblclick", () => {
|
||||||
|
if (!isDir(e)) source.open(e)
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- The chosen file's details and preview.
|
||||||
|
const choose = (e) => {
|
||||||
|
chosen = e
|
||||||
|
for (const r of el.list.querySelectorAll(".frow[data-path]")) r.classList.toggle("is-chosen", r.dataset.path === e.path)
|
||||||
|
drawDetails()
|
||||||
|
}
|
||||||
|
const drawDetails = () => {
|
||||||
|
const box = el.details
|
||||||
|
if (!box) return
|
||||||
|
for (const r of el.list.querySelectorAll(".frow.is-chosen")) if (!chosen || r.dataset.path !== chosen.path) r.classList.remove("is-chosen")
|
||||||
|
clear(box)
|
||||||
|
if (!chosen) {
|
||||||
|
box.append(h("div", { class: "fdetails__none" }, icon(ICONS.file), h("p", { text: t("Choose a file to see it here.") })))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const e = chosen
|
||||||
|
const date = dateOf(e.updated_at)
|
||||||
|
const fact = (label, value, title) => (value ? [h("dt", { text: label }), h("dd", { title: title || value, text: value })] : [])
|
||||||
|
const preview = h("div", { class: "fdetails__preview", "aria-label": t("Preview") })
|
||||||
|
box.append(
|
||||||
|
h("div", { class: "fdetails__id" }, h("span", { class: `frow__icon frow__icon--${showKind(e.name).kind}` }, icon(iconOf(e))), h("h2", { class: "fdetails__name", title: e.name, text: e.name })),
|
||||||
|
h("dl", { class: "fdetails__facts" }, fact(t("Type"), typeOf(e)), fact(t("Size"), bytes(e.size)), fact(t("Changed"), date.short, date.full), fact(t("Path"), e.path)),
|
||||||
|
h("div", { class: "fdetails__do" }, h("button", { type: "button", class: "btn btn--sm btn--primary", text: t("Open in the viewer"), onclick: () => source.open(e) })),
|
||||||
|
preview,
|
||||||
|
)
|
||||||
|
reading(preview, {
|
||||||
|
load: () => source.preview(e),
|
||||||
|
draw: (f) => {
|
||||||
|
if (chosen !== e) return
|
||||||
|
if (f.withheld) preview.append(h("p", { class: "fdetails__note", text: withheldWords(f.withheld, f.size ?? e.size) }))
|
||||||
|
else {
|
||||||
|
const text = String(f.text ?? "")
|
||||||
|
preview.append(h("pre", { class: "fdetails__text", text: text.length > PREVIEW_CHARS ? `${text.slice(0, PREVIEW_CHARS)}\n…` : text }))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read again: the list, the tree as it is open, the file chosen. */
|
||||||
|
const refresh = () => {
|
||||||
|
pending.clear()
|
||||||
|
const at = current ?? source.top()
|
||||||
|
drawTree()
|
||||||
|
return go(at)
|
||||||
|
}
|
||||||
|
/** A directory changed (an upload, a new folder, a rename, a delete): draw what is there now. */
|
||||||
|
const changed = (dir) => {
|
||||||
|
pending.clear()
|
||||||
|
const b = branches.get(dir)
|
||||||
|
if (b) branch(b.ul, dir, b.depth)
|
||||||
|
if (dir === current || source.ask(dir) === source.ask(current ?? source.top())) return go(current ?? source.top())
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
return { go, refresh, changed, drawTree, current: () => current ?? source.top() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- This computer ------------------------------------------------------------------------------
|
// --- This computer ------------------------------------------------------------------------------
|
||||||
|
|
||||||
let local = { root: "", roots: [], fixed: false }
|
let local = { root: "", roots: [], fixed: false }
|
||||||
|
let localPane = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Point this computer's pane at a folder: `fixed` (an agent chat's working directory, which is
|
* Point this computer's pane at a folder: `fixed` (an agent chat's working directory, which is
|
||||||
@@ -147,32 +382,63 @@ export function showLocal({ roots, root, fixed }) {
|
|||||||
if (changed) renderLocal()
|
if (changed) renderLocal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const baseName = (p) => String(p).replace(/\/+$/, "").split("/").pop() || p
|
||||||
|
|
||||||
function renderLocal() {
|
function renderLocal() {
|
||||||
const head = clear($("local-where"))
|
const head = clear($("local-where"))
|
||||||
const box = $("local-tree")
|
const els = { crumbs: $("local-crumbs"), tree: $("local-tree"), list: $("local-list"), details: $("local-details") }
|
||||||
|
$("pane-local").dataset.empty = String(!local.roots.length)
|
||||||
if (!local.roots.length) {
|
if (!local.roots.length) {
|
||||||
clear(box).append(
|
clear(els.crumbs)
|
||||||
h("p", { class: "pane__note", text: t("No project folder yet: the agent can open nothing on this computer.") }),
|
clear(els.tree)
|
||||||
h("button", { type: "button", class: "btn btn--sm btn--primary", text: t("Add folder…"), onclick: () => invoke("remote_add") }),
|
clear(els.details)
|
||||||
|
clear(els.list).append(
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ class: "fx__empty" },
|
||||||
|
icon(ICONS.folder),
|
||||||
|
h("p", { text: t("No project folder yet: the agent can open nothing on this computer.") }),
|
||||||
|
h("button", { type: "button", class: "btn btn--sm btn--primary", text: t("Add folder…"), onclick: () => invoke("remote_add") }),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
localPane = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (local.fixed) {
|
if (!local.fixed && local.roots.length > 1) {
|
||||||
head.append(h("span", { class: "pane__path", title: local.root }, h("bdi", { text: local.root })))
|
head.append(
|
||||||
} else {
|
h(
|
||||||
const pick = h(
|
"select",
|
||||||
"select",
|
{ class: "pane__pick", "aria-label": t("Folder"), title: local.root, onchange: (e) => showLocal({ roots: local.roots, root: e.target.value, fixed: false }) },
|
||||||
{ class: "pane__pick", "aria-label": t("Folder"), onchange: (e) => showLocal({ roots: local.roots, root: e.target.value, fixed: false }) },
|
local.roots.map((r) => h("option", { value: r, text: r, selected: r === local.root })),
|
||||||
local.roots.map((r) => h("option", { value: r, text: r, selected: r === local.root })),
|
),
|
||||||
)
|
)
|
||||||
head.append(pick)
|
|
||||||
}
|
}
|
||||||
const root = local.root
|
const root = local.root
|
||||||
return tree(box, {
|
const where = t("This computer")
|
||||||
rootPath: "",
|
localPane = explorer(els, {
|
||||||
list: async (path) => (await invoke("local_list", { root, path })).entries,
|
key: "local",
|
||||||
|
top: () => "",
|
||||||
|
ask: (path) => path || "",
|
||||||
|
rootName: () => baseName(root),
|
||||||
|
crumbs: (path) => {
|
||||||
|
const out = [[baseName(root), ""]]
|
||||||
|
let at = ""
|
||||||
|
for (const part of String(path).split("/").filter(Boolean)) {
|
||||||
|
at = at ? `${at}/${part}` : part
|
||||||
|
out.push([part, at])
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
},
|
||||||
|
list: async (path) => {
|
||||||
|
const r = await invoke("local_list", { root, path })
|
||||||
|
if (!r || !Array.isArray(r.entries)) throw { detail: t("The server's answer could not be read.") }
|
||||||
|
return {
|
||||||
|
path: r.path ?? path,
|
||||||
|
entries: r.entries.map((e) => ({ ...e, updated_at: e.modified ? new Date(e.modified * 1000).toISOString() : null })),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preview: (e) => invoke("local_read", { root, path: e.path }),
|
||||||
open: async (e) => {
|
open: async (e) => {
|
||||||
const where = t("This computer")
|
|
||||||
try {
|
try {
|
||||||
const f = await invoke("local_read", { root, path: e.path })
|
const f = await invoke("local_read", { root, path: e.path })
|
||||||
openViewer({ name: f.name, path: `${f.root}/${f.path}`, where, text: f.text, withheld: f.withheld, size: f.size })
|
openViewer({ name: f.name, path: `${f.root}/${f.path}`, where, text: f.text, withheld: f.withheld, size: f.size })
|
||||||
@@ -181,6 +447,8 @@ function renderLocal() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
localPane.drawTree()
|
||||||
|
localPane.go("")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Read a file the agent showed in a chat on this computer: `path` from the chat's folder. */
|
/** Read a file the agent showed in a chat on this computer: `path` from the chat's folder. */
|
||||||
@@ -201,11 +469,11 @@ export async function readServer(path) {
|
|||||||
// POST /files/rename { path, name }
|
// POST /files/rename { path, name }
|
||||||
// DELETE /files?path= to the trash, as on the web
|
// DELETE /files?path= to the trash, as on the web
|
||||||
|
|
||||||
let server = null // the tree drawn now: { reload(path) }
|
let server = null // the explorer drawn now
|
||||||
let serverRoot = "/" // the directory the server answered `/` with: the person's home
|
let serverRoot = "/" // the directory the server answered `/` with: `/` or the person's home
|
||||||
|
|
||||||
const parentOf = (path) => path.replace(/\/[^/]*$/, "") || "/"
|
const parentOf = (path) => path.replace(/\/[^/]*$/, "") || "/"
|
||||||
const reloadServer = (dir) => server?.reload(dir === serverRoot ? "/" : dir)
|
const reloadServer = (dir) => server?.changed(dir)
|
||||||
|
|
||||||
function serverSaid(text, error = false) {
|
function serverSaid(text, error = false) {
|
||||||
const line = $("server-status")
|
const line = $("server-status")
|
||||||
@@ -268,24 +536,48 @@ async function deleteEntry(e) {
|
|||||||
function serverActions(e) {
|
function serverActions(e) {
|
||||||
const items = []
|
const items = []
|
||||||
if (isDir(e)) items.push({ label: t("Upload"), run: () => uploadInto(e.path) }, { label: t("New folder"), run: () => makeFolder(e.path) })
|
if (isDir(e)) items.push({ label: t("Upload"), run: () => uploadInto(e.path) }, { label: t("New folder"), run: () => makeFolder(e.path) })
|
||||||
if (e.kind !== "mount") items.push(...(items.length ? [null] : []), { label: t("Rename"), run: () => renameEntry(e) }, { label: t("Delete"), danger: true, run: () => deleteEntry(e) })
|
else items.push({ label: t("Open in the viewer"), run: () => openServerFile(e.path, e.name) })
|
||||||
|
if (e.kind !== "mount") items.push(null, { label: t("Rename"), run: () => renameEntry(e) }, { label: t("Delete"), danger: true, run: () => deleteEntry(e) })
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderServer() {
|
export function renderServer() {
|
||||||
const box = $("server-tree")
|
|
||||||
serverSaid("")
|
serverSaid("")
|
||||||
server = tree(box, {
|
const els = { crumbs: $("server-crumbs"), tree: $("server-tree"), list: $("server-list"), details: $("server-details") }
|
||||||
rootPath: "/",
|
let first = true
|
||||||
|
server = explorer(els, {
|
||||||
|
key: "server",
|
||||||
|
top: () => serverRoot,
|
||||||
|
// The top is asked as `/`, which is what the server opens a person at.
|
||||||
|
ask: (path) => (!path || path === serverRoot ? "/" : path),
|
||||||
|
rootName: () => (serverRoot === "/" ? t("All files") : t("Home")),
|
||||||
|
crumbs: (path) => {
|
||||||
|
const out = [[serverRoot === "/" ? t("All files") : t("Home"), serverRoot]]
|
||||||
|
const rest = path.startsWith(serverRoot) ? path.slice(serverRoot.length) : path
|
||||||
|
let at = serverRoot
|
||||||
|
for (const part of rest.split("/").filter(Boolean)) {
|
||||||
|
at = `${at.replace(/\/$/, "")}/${part}`
|
||||||
|
out.push([part, at])
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
},
|
||||||
list: async (path) => {
|
list: async (path) => {
|
||||||
const r = await get(SERVER_FILES.list(path))
|
const r = await get(SERVER_FILES.list(path))
|
||||||
if (!r || !Array.isArray(r.entries)) throw { detail: t("The server's answer could not be read.") }
|
if (!r || !Array.isArray(r.entries)) throw { detail: t("The server's answer could not be read.") }
|
||||||
if (path === "/" && r.path) serverRoot = r.path
|
if (path === "/" && r.path && r.path !== serverRoot) {
|
||||||
return r.entries
|
serverRoot = r.path
|
||||||
|
// The top is known now: the tree and the crumbs start there.
|
||||||
|
if (first) queueMicrotask(() => server?.drawTree())
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
return r
|
||||||
},
|
},
|
||||||
|
preview: (e) => readServer(e.path),
|
||||||
open: (e) => openServerFile(e.path, e.name),
|
open: (e) => openServerFile(e.path, e.name),
|
||||||
actions: serverActions,
|
actions: serverActions,
|
||||||
})
|
})
|
||||||
|
server.drawTree()
|
||||||
|
return server.go(serverRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A file in the server's tree, in the viewer: from the explorer and from a chat's outputs. */
|
/** A file in the server's tree, in the viewer: from the explorer and from a chat's outputs. */
|
||||||
@@ -312,7 +604,7 @@ export async function openServerFile(path, name) {
|
|||||||
let tab = "local"
|
let tab = "local"
|
||||||
let serverRead = false
|
let serverRead = false
|
||||||
|
|
||||||
/** Show one of the two trees: `local` (this computer) or `server`. The server's is read once. */
|
/** Show one of the two explorers: `local` (this computer) or `server`. The server's is read once. */
|
||||||
export function showFilesTab(which) {
|
export function showFilesTab(which) {
|
||||||
tab = which === "server" ? "server" : "local"
|
tab = which === "server" ? "server" : "local"
|
||||||
for (const b of $("files-tabs").querySelectorAll("button")) b.setAttribute("aria-selected", String(b.dataset.tab === tab))
|
for (const b of $("files-tabs").querySelectorAll("button")) b.setAttribute("aria-selected", String(b.dataset.tab === tab))
|
||||||
@@ -328,13 +620,15 @@ export function initExplorer() {
|
|||||||
const tabBtn = (which, ico, label) =>
|
const tabBtn = (which, ico, label) =>
|
||||||
h("button", { type: "button", role: "tab", class: "tabs__tab", dataset: { tab: which }, "aria-selected": String(which === tab), onclick: () => showFilesTab(which) }, icon(ico), h("span", { text: label }))
|
h("button", { type: "button", role: "tab", class: "tabs__tab", dataset: { tab: which }, "aria-selected": String(which === tab), onclick: () => showFilesTab(which) }, icon(ico), h("span", { text: label }))
|
||||||
$("files-tabs").replaceChildren(tabBtn("local", ICONS.folder, t("This computer")), tabBtn("server", ICONS.file, t("Server")))
|
$("files-tabs").replaceChildren(tabBtn("local", ICONS.folder, t("This computer")), tabBtn("server", ICONS.file, t("Server")))
|
||||||
$("server-where").textContent = t("Your files on the server, as the chats see them.")
|
|
||||||
$("server-refresh").replaceChildren(icon(ICONS.refresh))
|
$("server-refresh").replaceChildren(icon(ICONS.refresh))
|
||||||
$("server-refresh").addEventListener("click", renderServer)
|
$("server-refresh").addEventListener("click", () => {
|
||||||
|
serverSaid("")
|
||||||
|
return server ? server.refresh() : renderServer()
|
||||||
|
})
|
||||||
$("server-upload").replaceChildren(icon(ICONS.send))
|
$("server-upload").replaceChildren(icon(ICONS.send))
|
||||||
$("server-upload").addEventListener("click", () => uploadInto(serverRoot))
|
$("server-upload").addEventListener("click", () => uploadInto(server?.current() ?? serverRoot))
|
||||||
$("server-mkdir").replaceChildren(icon(ICONS.folder))
|
$("server-mkdir").replaceChildren(icon(ICONS.folder))
|
||||||
$("server-mkdir").addEventListener("click", () => makeFolder(serverRoot))
|
$("server-mkdir").addEventListener("click", () => makeFolder(server?.current() ?? serverRoot))
|
||||||
$("local-refresh").replaceChildren(icon(ICONS.refresh))
|
$("local-refresh").replaceChildren(icon(ICONS.refresh))
|
||||||
$("local-refresh").addEventListener("click", renderLocal)
|
$("local-refresh").addEventListener("click", () => (localPane ? localPane.refresh() : renderLocal()))
|
||||||
}
|
}
|
||||||
+1
-1
@@ -52,7 +52,7 @@ export function closeViewer() {
|
|||||||
* server's is { reason: "not_text" | "too_large", message } with the message in the person's
|
* server's is { reason: "not_text" | "too_large", message } with the message in the person's
|
||||||
* language already.
|
* language already.
|
||||||
*/
|
*/
|
||||||
function withheldWords(withheld, size) {
|
export function withheldWords(withheld, size) {
|
||||||
if (typeof withheld === "object" && withheld.message) return String(withheld.message)
|
if (typeof withheld === "object" && withheld.message) return String(withheld.message)
|
||||||
const reason = typeof withheld === "object" ? withheld.reason : withheld
|
const reason = typeof withheld === "object" ? withheld.reason : withheld
|
||||||
return reason === "too_big" || reason === "too_large"
|
return reason === "too_big" || reason === "too_large"
|
||||||
|
|||||||
Reference in new issue
Block a user