diff --git a/CHANGELOG.md b/CHANGELOG.md index 841eb6a..c745b8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,44 @@ All notable changes to LLeMbas Desktop. The format follows ## [Unreleased] +### Added + +- **Attachments.** A message can carry files: the attach button beside the composer (the + system's file dialog), files dropped anywhere on the window, or an image pasted into the + composer. Each is sent to the server at once and shown above the composer with its size, or + with the server's reason when it refused it; one taken off before sending is deleted on the + server. The app sends only files you chose, dropped or pasted -- the page never names a path. + Agent chats get them too. +- **Edit a message, regenerate a reply, copy either.** Under your messages, *Edit message* + rewrites one in place and starts the reply again from there (everything after it is dropped, + as the app says before you save; on the agent's device too); under the last reply, + *Regenerate reply*. *Copy* puts a message, or a reply's text as Markdown, on the clipboard. +- **The chat's own menu** (⋯ in its header, or a right click on it in the sidebar): rename it, + open it in the web interface in your browser, archive it, delete it (asked first). +- **Change a chat's model** from its header, and its reasoning effort where the model offers + more than one level; a new chat can choose its effort too. +- **Model avatars**: every model in the pickers and under each reply shows its initial on a + colour of its own -- the same colour the web interface gives it. +- **A command palette** (Ctrl+K): new chats, settings, the agent and updates by name, and every + chat of both kinds by its title (accents optional). Ctrl+N starts a new chat of the tab you are + on; Ctrl+, opens the settings. + +### Changed + +- **The agent's status is the CLI's own word.** The title bar and Settings no longer say + *running* because a process runs: they say what the built-in LLeMbas CLI says of its link to + the server -- connecting, running (linked), waiting and why (the server cannot be reached, the + link dropped, a terminal on this computer holds the link), or refused and why. The same for a + `lembas service` you run yourself. When the CLI says trying again will not help (the server + refused this computer's token), the app no longer restarts it every second: it waits until you + link the computer again. When the agent stops with an error, its last words are the reason + shown, not just an exit status. + +### Fixed + +- A Czech sentence in the viewer was in Slovak; "Server files" and the composer's placeholder + were not translated. + ## [1.0.0-beta.2] — 2026-11-07 Carries LLeMbas CLI 2.0.0-beta.2 and the look of LLeMbas 2.0.0-beta.4. diff --git a/README.md b/README.md index d2d428b..5bb3603 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,11 @@ an app of its own. on the server. - **A viewer** for what you open and for what the agent shows you: Markdown drawn, code highlighted, text, and HTML in a sealed frame that can reach neither the app nor the network. +- **Attachments** from the file dialog, a drop on the window or a paste; **edit** a message, + **regenerate** a reply, rename, archive or delete a chat, and open it in the web interface. +- **A command palette** (Ctrl+K) for commands and every chat by its title. +- **The agent's real status**: what the built-in CLI says of its link -- connecting, running, + waiting and why -- not whether a process runs. - **Notifications** when a reply or an agent's work finishes while you are elsewhere. - **Its own window and tray**: close the window and the agent keeps running in the tray. @@ -73,6 +78,8 @@ device. directory, readable by you only); the page asks for the server's `/api/v2` through it and never holds the token. - The explorer reads only inside the agent's folders. +- A file goes to the server as an attachment only when you choose it in the file dialog, drop it + on the window or paste it: the page cannot name a path for the app to send. - HTML is shown in a sealed frame on an address of its own: scripts in it run, but it has no access to the app, no network and no way to navigate anywhere. - Links in a reply open in your browser, and only web and mail links. diff --git a/scripts/gen-i18n.py b/scripts/gen-i18n.py index 6511ffb..a4a1e44 100755 --- a/scripts/gen-i18n.py +++ b/scripts/gen-i18n.py @@ -156,7 +156,6 @@ KEYS = ( "agent running (service)", "agent stopped", "agent: no folders", - "Agent stopped: %(why)s", "update %(version)s", "update", "LLeMbas %(version)s is available (this is %(current)s)", @@ -167,6 +166,41 @@ KEYS = ( "Page", "Preview", "Source", + # 1.0 for daily use: attachments, message and chat actions, the palette, the agent's word + "This is a folder; attach the files in it.", + "This file is too large to send.", + "Sending…", + "Saving starts the reply again; everything after this message is dropped.", + "Rename…", + "Open in the web interface", + "Delete this chat?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.", + "Wait until the files are sent.", + "Type a command or a chat's title", + "The agent is connecting to the server…", + "The agent is not connected: %(why)s. It keeps trying.", + "The agent will not connect: %(why)s. Link this computer again.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s", + "agent connecting…", + "agent waiting", + "agent refused", + "Agent: %(why)s", + "Server files", + "Drop the files to attach them", + "Attach files", + "Edit message", + "Save", + "Rename chat", + "Rename", + "Delete", + "Search", + "Nothing matches.", + "Message", + "Archive chat", + "Regenerate reply", + "Delete chat", + "Reasoning effort", + "Commands", ) # The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the @@ -265,7 +299,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "agent běží (služba)", "agent stopped": "agent zastaven", "agent: no folders": "agent: žádné složky", - "Agent stopped: %(why)s": "Agent se zastavil: %(why)s", "update %(version)s": "aktualizace %(version)s", "update": "aktualizace", "LLeMbas %(version)s is available (this is %(current)s)": "Je k dispozici LLeMbas %(version)s (tohle je %(current)s)", @@ -273,6 +306,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "Toto není textový soubor; nelze ho zde zobrazit.", "Page": "Stránka", "Source": "Zdroj", + "This is a folder; attach the files in it.": "Toto je složka; přiložte soubory v ní.", + "This file is too large to send.": "Tento soubor je příliš velký na odeslání.", + "Sending…": "Odesílá se…", + "Saving starts the reply again; everything after this message is dropped.": "Uložením se odpověď spustí znovu; vše po této zprávě se zahodí.", + "Rename…": "Přejmenovat…", + "Open in the web interface": "Otevřít ve webovém rozhraní", + "Delete this chat?": "Smazat tuto konverzaci?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s“ se smaže na serveru i se svými zprávami a soubory. Nelze to vrátit.", + "Wait until the files are sent.": "Počkejte, až se soubory odešlou.", + "Type a command or a chat's title": "Napište příkaz nebo název konverzace", + "The agent is connecting to the server…": "Agent se připojuje k serveru…", + "The agent is not connected: %(why)s. It keeps trying.": "Agent není připojený: %(why)s. Zkouší to dál.", + "The agent will not connect: %(why)s. Link this computer again.": "Agent se nepřipojí: %(why)s. Propojte tento počítač znovu.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Služba LLeMbas CLI už tu běží (pid %(pid)s), ale není připojená: %(why)s", + "agent connecting…": "agent se připojuje…", + "agent waiting": "agent čeká", + "agent refused": "agent odmítnut", + "Agent: %(why)s": "Agent: %(why)s", + "Server files": "Soubory na serveru", + "Drop the files to attach them": "Pusťte soubory a přiloží se", }, "de": { "Menu": "Menü", @@ -366,7 +419,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "Agent läuft (Dienst)", "agent stopped": "Agent angehalten", "agent: no folders": "Agent: keine Ordner", - "Agent stopped: %(why)s": "Agent angehalten: %(why)s", "update %(version)s": "Update %(version)s", "update": "Update", "LLeMbas %(version)s is available (this is %(current)s)": "LLeMbas %(version)s ist verfügbar (dies ist %(current)s)", @@ -374,6 +426,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "Das ist keine Textdatei; sie kann hier nicht angezeigt werden.", "Page": "Seite", "Source": "Quelltext", + "This is a folder; attach the files in it.": "Das ist ein Ordner; hängen Sie die Dateien darin an.", + "This file is too large to send.": "Diese Datei ist zu groß zum Senden.", + "Sending…": "Wird gesendet…", + "Saving starts the reply again; everything after this message is dropped.": "Speichern startet die Antwort neu; alles nach dieser Nachricht wird verworfen.", + "Rename…": "Umbenennen…", + "Open in the web interface": "In der Weboberfläche öffnen", + "Delete this chat?": "Diesen Chat löschen?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s“ wird auf dem Server gelöscht, mit seinen Nachrichten und Dateien. Das lässt sich nicht rückgängig machen.", + "Wait until the files are sent.": "Warten Sie, bis die Dateien gesendet sind.", + "Type a command or a chat's title": "Befehl oder Chat-Titel eingeben", + "The agent is connecting to the server…": "Der Agent verbindet sich mit dem Server…", + "The agent is not connected: %(why)s. It keeps trying.": "Der Agent ist nicht verbunden: %(why)s. Er versucht es weiter.", + "The agent will not connect: %(why)s. Link this computer again.": "Der Agent verbindet sich nicht: %(why)s. Verknüpfen Sie diesen Computer erneut.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Ein LLeMbas-CLI-Dienst läuft hier bereits (PID %(pid)s), ist aber nicht verbunden: %(why)s", + "agent connecting…": "Agent verbindet…", + "agent waiting": "Agent wartet", + "agent refused": "Agent abgewiesen", + "Agent: %(why)s": "Agent: %(why)s", + "Server files": "Dateien auf dem Server", + "Drop the files to attach them": "Dateien ablegen, um sie anzuhängen", }, "es": { "Menu": "Menú", @@ -467,7 +539,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "agente en marcha (servicio)", "agent stopped": "agente detenido", "agent: no folders": "agente: sin carpetas", - "Agent stopped: %(why)s": "El agente se detuvo: %(why)s", "update %(version)s": "actualizar a %(version)s", "update": "actualizar", "LLeMbas %(version)s is available (this is %(current)s)": "LLeMbas %(version)s está disponible (esta es la %(current)s)", @@ -475,6 +546,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "No es un archivo de texto; no se puede mostrar aquí.", "Page": "Página", "Source": "Código fuente", + "This is a folder; attach the files in it.": "Esto es una carpeta; adjunte los archivos que contiene.", + "This file is too large to send.": "Este archivo es demasiado grande para enviarlo.", + "Sending…": "Enviando…", + "Saving starts the reply again; everything after this message is dropped.": "Al guardar, la respuesta empieza de nuevo; se descarta todo lo que hay después de este mensaje.", + "Rename…": "Renombrar…", + "Open in the web interface": "Abrir en la interfaz web", + "Delete this chat?": "¿Eliminar este chat?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "«%(title)s» se elimina del servidor, con sus mensajes y archivos. No se puede deshacer.", + "Wait until the files are sent.": "Espere a que se envíen los archivos.", + "Type a command or a chat's title": "Escriba un comando o el título de un chat", + "The agent is connecting to the server…": "El agente se está conectando al servidor…", + "The agent is not connected: %(why)s. It keeps trying.": "El agente no está conectado: %(why)s. Sigue intentándolo.", + "The agent will not connect: %(why)s. Link this computer again.": "El agente no se conectará: %(why)s. Vuelva a vincular este equipo.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Ya hay un servicio de LLeMbas CLI aquí (pid %(pid)s), pero no está conectado: %(why)s", + "agent connecting…": "agente conectando…", + "agent waiting": "agente en espera", + "agent refused": "agente rechazado", + "Agent: %(why)s": "Agente: %(why)s", + "Server files": "Archivos del servidor", + "Drop the files to attach them": "Suelte los archivos para adjuntarlos", }, "fr": { "Menu": "Menu", @@ -568,7 +659,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "agent actif (service)", "agent stopped": "agent interrompu", "agent: no folders": "agent : aucun dossier", - "Agent stopped: %(why)s": "L'agent s'est arrêté : %(why)s", "update %(version)s": "mise à jour %(version)s", "update": "mise à jour", "LLeMbas %(version)s is available (this is %(current)s)": "LLeMbas %(version)s est disponible (ceci est la %(current)s)", @@ -576,6 +666,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "Ce n'est pas un fichier texte ; il ne peut pas être affiché ici.", "Page": "Page", "Source": "Source", + "This is a folder; attach the files in it.": "C'est un dossier ; joignez les fichiers qu'il contient.", + "This file is too large to send.": "Ce fichier est trop volumineux pour être envoyé.", + "Sending…": "Envoi…", + "Saving starts the reply again; everything after this message is dropped.": "Enregistrer relance la réponse ; tout ce qui suit ce message est supprimé.", + "Rename…": "Renommer…", + "Open in the web interface": "Ouvrir dans l'interface web", + "Delete this chat?": "Supprimer cette conversation ?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "« %(title)s » est supprimée du serveur, avec ses messages et ses fichiers. C'est définitif.", + "Wait until the files are sent.": "Attendez que les fichiers soient envoyés.", + "Type a command or a chat's title": "Tapez une commande ou le titre d'une conversation", + "The agent is connecting to the server…": "L'agent se connecte au serveur…", + "The agent is not connected: %(why)s. It keeps trying.": "L'agent n'est pas connecté : %(why)s. Il réessaie.", + "The agent will not connect: %(why)s. Link this computer again.": "L'agent ne se connectera pas : %(why)s. Reliez à nouveau cet ordinateur.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Un service LLeMbas CLI tourne déjà ici (pid %(pid)s), mais n'est pas connecté : %(why)s", + "agent connecting…": "agent en connexion…", + "agent waiting": "agent en attente", + "agent refused": "agent refusé", + "Agent: %(why)s": "Agent : %(why)s", + "Server files": "Fichiers du serveur", + "Drop the files to attach them": "Déposez les fichiers pour les joindre", }, "hu": { "Menu": "Menü", @@ -669,7 +779,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "ügynök fut (szolgáltatás)", "agent stopped": "ügynök leállt", "agent: no folders": "ügynök: nincs mappa", - "Agent stopped: %(why)s": "Az ügynök leállt: %(why)s", "update %(version)s": "frissítés: %(version)s", "update": "frissítés", "LLeMbas %(version)s is available (this is %(current)s)": "Elérhető a LLeMbas %(version)s (ez a %(current)s)", @@ -677,6 +786,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "Ez nem szövegfájl; itt nem jeleníthető meg.", "Page": "Oldal", "Source": "Forrás", + "This is a folder; attach the files in it.": "Ez egy mappa; a benne lévő fájlokat csatolja.", + "This file is too large to send.": "Ez a fájl túl nagy a küldéshez.", + "Sending…": "Küldés…", + "Saving starts the reply again; everything after this message is dropped.": "A mentés újraindítja a választ; minden, ami ez után az üzenet után jön, elvész.", + "Rename…": "Átnevezés…", + "Open in the web interface": "Megnyitás a webes felületen", + "Delete this chat?": "Törli ezt a csevegést?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s” törlődik a szerverről, az üzeneteivel és fájljaival együtt. Ez nem vonható vissza.", + "Wait until the files are sent.": "Várjon, amíg a fájlok elküldődnek.", + "Type a command or a chat's title": "Írjon be egy parancsot vagy egy csevegés címét", + "The agent is connecting to the server…": "Az ügynök csatlakozik a szerverhez…", + "The agent is not connected: %(why)s. It keeps trying.": "Az ügynök nincs csatlakozva: %(why)s. Tovább próbálkozik.", + "The agent will not connect: %(why)s. Link this computer again.": "Az ügynök nem fog csatlakozni: %(why)s. Kapcsolja össze újra ezt a számítógépet.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Egy LLeMbas CLI szolgáltatás már fut itt (pid %(pid)s), de nincs csatlakozva: %(why)s", + "agent connecting…": "ügynök csatlakozik…", + "agent waiting": "ügynök vár", + "agent refused": "ügynök elutasítva", + "Agent: %(why)s": "Ügynök: %(why)s", + "Server files": "Fájlok a szerveren", + "Drop the files to attach them": "Engedje el a fájlokat a csatoláshoz", }, "it": { "Menu": "Menu", @@ -770,7 +899,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "agente attivo (servizio)", "agent stopped": "agente fermato", "agent: no folders": "agente: nessuna cartella", - "Agent stopped: %(why)s": "L'agente si è fermato: %(why)s", "update %(version)s": "aggiorna a %(version)s", "update": "aggiorna", "LLeMbas %(version)s is available (this is %(current)s)": "È disponibile LLeMbas %(version)s (questa è la %(current)s)", @@ -778,6 +906,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "Questo non è un file di testo; non può essere mostrato qui.", "Page": "Pagina", "Source": "Sorgente", + "This is a folder; attach the files in it.": "Questa è una cartella; alleghi i file che contiene.", + "This file is too large to send.": "Questo file è troppo grande per essere inviato.", + "Sending…": "Invio…", + "Saving starts the reply again; everything after this message is dropped.": "Salvando, la risposta riparte; tutto ciò che segue questo messaggio viene scartato.", + "Rename…": "Rinomina…", + "Open in the web interface": "Apri nell'interfaccia web", + "Delete this chat?": "Eliminare questa chat?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "«%(title)s» viene eliminata dal server, con i suoi messaggi e file. Non si può annullare.", + "Wait until the files are sent.": "Attenda che i file siano inviati.", + "Type a command or a chat's title": "Digiti un comando o il titolo di una chat", + "The agent is connecting to the server…": "L'agente si sta collegando al server…", + "The agent is not connected: %(why)s. It keeps trying.": "L'agente non è collegato: %(why)s. Continua a riprovare.", + "The agent will not connect: %(why)s. Link this computer again.": "L'agente non si collegherà: %(why)s. Colleghi di nuovo questo computer.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Un servizio LLeMbas CLI è già in esecuzione qui (pid %(pid)s), ma non è collegato: %(why)s", + "agent connecting…": "agente in collegamento…", + "agent waiting": "agente in attesa", + "agent refused": "agente rifiutato", + "Agent: %(why)s": "Agente: %(why)s", + "Server files": "File sul server", + "Drop the files to attach them": "Rilasci i file per allegarli", }, "pl": { "Menu": "Menu", @@ -871,7 +1019,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "agent działa (usługa)", "agent stopped": "agent zatrzymany", "agent: no folders": "agent: brak folderów", - "Agent stopped: %(why)s": "Agent się zatrzymał: %(why)s", "update %(version)s": "aktualizacja %(version)s", "update": "aktualizacja", "LLeMbas %(version)s is available (this is %(current)s)": "Dostępny jest LLeMbas %(version)s (to jest %(current)s)", @@ -879,6 +1026,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "To nie jest plik tekstowy; nie można go tu pokazać.", "Page": "Strona", "Source": "Źródło", + "This is a folder; attach the files in it.": "To jest folder; dołącz pliki, które zawiera.", + "This file is too large to send.": "Ten plik jest za duży, aby go wysłać.", + "Sending…": "Wysyłanie…", + "Saving starts the reply again; everything after this message is dropped.": "Zapisanie uruchamia odpowiedź od nowa; wszystko po tej wiadomości zostanie odrzucone.", + "Rename…": "Zmień nazwę…", + "Open in the web interface": "Otwórz w interfejsie WWW", + "Delete this chat?": "Usunąć ten czat?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s” zostanie usunięty z serwera wraz z wiadomościami i plikami. Tego nie można cofnąć.", + "Wait until the files are sent.": "Poczekaj, aż pliki zostaną wysłane.", + "Type a command or a chat's title": "Wpisz polecenie lub tytuł czatu", + "The agent is connecting to the server…": "Agent łączy się z serwerem…", + "The agent is not connected: %(why)s. It keeps trying.": "Agent nie jest połączony: %(why)s. Próbuje dalej.", + "The agent will not connect: %(why)s. Link this computer again.": "Agent się nie połączy: %(why)s. Połącz ten komputer ponownie.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Usługa LLeMbas CLI już tu działa (pid %(pid)s), ale nie jest połączona: %(why)s", + "agent connecting…": "agent łączy się…", + "agent waiting": "agent czeka", + "agent refused": "agent odrzucony", + "Agent: %(why)s": "Agent: %(why)s", + "Server files": "Pliki na serwerze", + "Drop the files to attach them": "Upuść pliki, aby je dołączyć", }, "sk": { "Menu": "Ponuka", @@ -972,7 +1139,6 @@ OWN: dict[str, dict[str, str]] = { "agent running (service)": "agent beží (služba)", "agent stopped": "agent zastavený", "agent: no folders": "agent: žiadne priečinky", - "Agent stopped: %(why)s": "Agent sa zastavil: %(why)s", "update %(version)s": "aktualizácia %(version)s", "update": "aktualizácia", "LLeMbas %(version)s is available (this is %(current)s)": "Je k dispozícii LLeMbas %(version)s (toto je %(current)s)", @@ -980,6 +1146,26 @@ OWN: dict[str, dict[str, str]] = { "This is not a text file; it cannot be shown here.": "Toto nie je textový súbor; nedá sa tu zobraziť.", "Page": "Stránka", "Source": "Zdroj", + "This is a folder; attach the files in it.": "Toto je priečinok; priložte súbory v ňom.", + "This file is too large to send.": "Tento súbor je príliš veľký na odoslanie.", + "Sending…": "Odosiela sa…", + "Saving starts the reply again; everything after this message is dropped.": "Uložením sa odpoveď spustí znova; všetko po tejto správe sa zahodí.", + "Rename…": "Premenovať…", + "Open in the web interface": "Otvoriť vo webovom rozhraní", + "Delete this chat?": "Zmazať túto konverzáciu?", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s“ sa zmaže na serveri aj so svojimi správami a súbormi. Nedá sa to vrátiť.", + "Wait until the files are sent.": "Počkajte, kým sa súbory odošlú.", + "Type a command or a chat's title": "Napíšte príkaz alebo názov konverzácie", + "The agent is connecting to the server…": "Agent sa pripája k serveru…", + "The agent is not connected: %(why)s. It keeps trying.": "Agent nie je pripojený: %(why)s. Skúša to ďalej.", + "The agent will not connect: %(why)s. Link this computer again.": "Agent sa nepripojí: %(why)s. Prepojte tento počítač znova.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Služba LLeMbas CLI tu už beží (pid %(pid)s), ale nie je pripojená: %(why)s", + "agent connecting…": "agent sa pripája…", + "agent waiting": "agent čaká", + "agent refused": "agent odmietnutý", + "Agent: %(why)s": "Agent: %(why)s", + "Server files": "Súbory na serveri", + "Drop the files to attach them": "Pustite súbory a priložia sa", }, } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index dff1c03..c9ee248 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -36,8 +36,8 @@ use tauri::menu::{Menu, MenuItem, PredefinedMenuItem}; use tauri::tray::TrayIconBuilder; use tauri::window::Color; use tauri::{ - AppHandle, Emitter, LogicalPosition, Manager, RunEvent, Url, WebviewUrl, WebviewWindow, - WebviewWindowBuilder, Window, WindowEvent, Wry, + AppHandle, DragDropEvent, Emitter, LogicalPosition, Manager, RunEvent, Url, WebviewUrl, + WebviewWindow, WebviewWindowBuilder, Window, WindowEvent, Wry, }; use tauri_plugin_dialog::{DialogExt, MessageDialogKind}; use tauri_plugin_notification::NotificationExt; @@ -114,6 +114,8 @@ struct App { next_stream: AtomicU64, sealed: sealed::Pages, watch: watch::Watch, + /// The files last dropped on the window: what `attach_dropped` may send, and nothing else. + dropped: Mutex>, } struct TrayItems { @@ -481,6 +483,173 @@ fn stream_close(app: tauri::State<'_, App>, id: u64) { } } +// --- Attachments ---------------------------------------------------------------------------------- + +/// What became of one file sent as an attachment: the server's attachment, or why not. `reason` +/// names the refusals made here, so the page can say them in its language; the server's own +/// refusals come in the account's language already. +#[derive(Clone, Serialize)] +struct Uploaded { + name: String, + size: u64, + attachment: Option, + error: Option, + reason: Option<&'static str>, +} + +fn read_for_upload(path: &std::path::Path) -> Result, (&'static str, String)> { + let meta = std::fs::metadata(path).map_err(|e| ("unreadable", e.to_string()))?; + if meta.is_dir() { + return Err(("folder", "This is a folder.".into())); + } + if meta.len() > server::UPLOAD_LIMIT { + return Err(("too_large", "This file is too large to send.".into())); + } + std::fs::read(path).map_err(|e| ("unreadable", e.to_string())) +} + +/// Send these files, one by one, as attachments for `chat` ("" for a chat not made yet). Only +/// ever called with paths the person chose: in the file dialog, or by dropping them on the window. +fn upload_paths( + handle: &AppHandle, + paths: Vec, + chat: &str, +) -> Result, ApiError> { + let Some((app_url, token)) = handle.state::().door() else { + return Err(ApiError::new(401, "Not signed in.")); + }; + let mut out = Vec::new(); + for path in paths { + let name = path + .file_name() + .map(|n| n.to_string_lossy().into_owned()) + .unwrap_or_else(|| "file".into()); + let size = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0); + let sent = match read_for_upload(&path) { + Ok(bytes) => server::upload(&app_url, &token, chat, &name, &bytes).map_err(|e| { + if e.status == 401 { + token_refused(handle); + } + (None, e.detail) + }), + Err((reason, message)) => Err((Some(reason), message)), + }; + out.push(match sent { + Ok(a) => Uploaded { + name, + size, + attachment: Some(a), + error: None, + reason: None, + }, + Err((reason, message)) => Uploaded { + name, + size, + attachment: None, + error: Some(message), + reason, + }, + }); + } + Ok(out) +} + +/// Choose files with the system's dialog and send them as attachments for `chat`. +#[tauri::command] +async fn attach_pick( + handle: AppHandle, + chat: Option, + title: String, +) -> Result, ApiError> { + let h = handle.clone(); + tauri::async_runtime::spawn_blocking(move || { + let picked = h.dialog().file().set_title(title).blocking_pick_files(); + let paths: Vec = picked + .unwrap_or_default() + .into_iter() + .filter_map(|p| p.into_path().ok()) + .collect(); + upload_paths(&h, paths, chat.as_deref().unwrap_or("")) + }) + .await + .map_err(|e| ApiError::new(0, e.to_string()))? +} + +/// Send the files just dropped on the window as attachments for `chat`. Each drop can be sent +/// once. +#[tauri::command] +async fn attach_dropped( + handle: AppHandle, + chat: Option, +) -> Result, ApiError> { + let paths = std::mem::take(&mut *handle.state::().dropped.lock().unwrap()); + let h = handle.clone(); + tauri::async_runtime::spawn_blocking(move || { + upload_paths(&h, paths, chat.as_deref().unwrap_or("")) + }) + .await + .map_err(|e| ApiError::new(0, e.to_string()))? +} + +/// `%XX` escapes undone (the page sends a file's name as `encodeURIComponent` gives it). +fn percent_decode(text: &str) -> String { + let b = text.as_bytes(); + let mut out = Vec::with_capacity(b.len()); + let mut i = 0; + while i < b.len() { + let hex = |c: u8| (c as char).to_digit(16); + if b[i] == b'%' && i + 2 < b.len() { + if let (Some(hi), Some(lo)) = (hex(b[i + 1]), hex(b[i + 2])) { + out.push((hi * 16 + lo) as u8); + i += 3; + continue; + } + } + out.push(b[i]); + i += 1; + } + String::from_utf8_lossy(&out).into_owned() +} + +/// A file pasted into the composer (an image from the clipboard): its bytes as the raw body of +/// the call, its name in `x-name` and the chat in `x-chat`. The page has these bytes only because +/// the person pasted them. +#[tauri::command] +async fn attach_bytes( + handle: AppHandle, + request: tauri::ipc::Request<'_>, +) -> Result { + let tauri::ipc::InvokeBody::Raw(bytes) = request.body() else { + return Err(ApiError::new(400, "No file.")); + }; + if bytes.len() as u64 > server::UPLOAD_LIMIT { + return Err(ApiError::new(413, "This file is too large to send.")); + } + let header = |k: &str| { + request + .headers() + .get(k) + .and_then(|v| v.to_str().ok()) + .unwrap_or("") + .to_string() + }; + let name = percent_decode(&header("x-name")); + let chat = header("x-chat"); + let bytes = bytes.clone(); + let Some((app_url, token)) = handle.state::().door() else { + return Err(ApiError::new(401, "Not signed in.")); + }; + let result = tauri::async_runtime::spawn_blocking(move || { + server::upload(&app_url, &token, &chat, &name, &bytes) + }) + .await + .map_err(|e| ApiError::new(0, e.to_string()))?; + if matches!(&result, Err(e) if e.status == 401) { + token_refused(&handle); + } + result +} + // --- This computer's files ---------------------------------------------------------------------- /// A folder of the agent's, for the explorer: `root` one of the agent's folders (or a folder @@ -1260,6 +1429,9 @@ pub fn run() { local_read, sealed_open, sealed_close, + attach_pick, + attach_dropped, + attach_bytes, set_theme, set_notify, set_open_chat, @@ -1314,6 +1486,7 @@ pub fn run() { next_stream: AtomicU64::new(0), sealed: sealed::Pages::default(), watch: watch::Watch::default(), + dropped: Mutex::new(Vec::new()), }); // Without a tray (no AppIndicator library, no StatusNotifier host) the app still // starts: the title bar's menu has every item the tray has. @@ -1345,6 +1518,30 @@ pub fn run() { WindowEvent::Resized(_) if window.label() == MAIN => { let _ = window.emit("maximized", window.is_maximized().unwrap_or(false)); } + // Files dragged onto the window: the page shows where they go, and a drop is held + // here for `attach_dropped` -- the page itself never names a path to send. + WindowEvent::DragDrop(ev) if window.label() == MAIN => match ev { + DragDropEvent::Enter { .. } => { + let _ = window.emit("drag", true); + } + DragDropEvent::Leave => { + let _ = window.emit("drag", false); + } + DragDropEvent::Drop { paths, .. } => { + let names: Vec = paths + .iter() + .map(|p| { + p.file_name() + .map(|n| n.to_string_lossy().into_owned()) + .unwrap_or_default() + }) + .collect(); + *window.state::().dropped.lock().unwrap() = paths.clone(); + let _ = window.emit("drag", false); + let _ = window.emit("drop", names); + } + _ => {} + }, _ => {} }) .build(tauri::generate_context!()); @@ -1407,6 +1604,14 @@ mod tests { } } + #[test] + fn a_pasted_files_name_comes_back_whole() { + assert_eq!(percent_decode("Sch%C3%B6n%20bild.png"), "Schön bild.png"); + assert_eq!(percent_decode("100%"), "100%"); + assert_eq!(percent_decode("a%zzb%4"), "a%zzb%4"); + assert_eq!(percent_decode("ž%41"), "žA"); + } + #[test] fn the_log_lives_in_the_apps_own_directory() { let f = log::file().unwrap(); diff --git a/src-tauri/src/server.rs b/src-tauri/src/server.rs index 35fa36c..26b6563 100644 --- a/src-tauri/src/server.rs +++ b/src-tauri/src/server.rs @@ -507,6 +507,85 @@ pub fn request_text(app: &str, token: &str, path: &str) -> Result String { + let clean: String = name + .chars() + .map(|c| match c { + '"' | '\\' => '_', + c if c.is_control() => '_', + c => c, + }) + .collect(); + if clean.trim().is_empty() { + "file".into() + } else { + clean + } +} + +/// The body of a `multipart/form-data` request with one file in the field `file`. +fn multipart(boundary: &str, name: &str, bytes: &[u8]) -> Vec { + let mut body = Vec::with_capacity(bytes.len() + 256); + body.extend_from_slice( + format!( + "--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{}\"\r\nContent-Type: application/octet-stream\r\n\r\n", + header_name(name) + ) + .as_bytes(), + ); + body.extend_from_slice(bytes); + body.extend_from_slice(format!("\r\n--{boundary}--\r\n").as_bytes()); + body +} + +/// Send one file to `/api/v2/attachments` (for the chat `chat`, or a chat not made yet): stored +/// now, bound to the message that names it when that is sent. The server's attachment, as JSON. +pub fn upload( + app: &str, + token: &str, + chat: &str, + name: &str, + bytes: &[u8], +) -> Result { + if !chat + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') + { + return Err(ApiError::new(400, format!("Not a chat: {chat}"))); + } + let path = if chat.is_empty() { + "/attachments".to_string() + } else { + format!("/attachments?chat={chat}") + }; + let nanos = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos()) + .unwrap_or(0); + let boundary = format!("llembas-desktop-{nanos:x}"); + let res = agent(Duration::from_secs(300)) + .post(&format!("{app}{path}")) + .set("authorization", &format!("Bearer {token}")) + .set("accept", "application/json") + .set( + "content-type", + &format!("multipart/form-data; boundary={boundary}"), + ) + .send_bytes(&multipart(&boundary, name, bytes))?; + let v: Value = res + .into_json() + .map_err(|_| ApiError::new(0, "The server's answer was not JSON.".to_string()))?; + v.get("attachment") + .cloned() + .ok_or_else(|| ApiError::new(0, "The server's answer was not understood.".to_string())) +} + /// Server-sent events, a line at a time: what `stream` hands on, one `(event, data)` per frame. #[derive(Default)] pub struct Sse { @@ -709,6 +788,18 @@ mod tests { } } + #[test] + fn a_file_goes_as_one_multipart_field() { + let body = multipart("XYZ", "a\"b\r\n.txt", b"hello"); + let text = String::from_utf8(body).unwrap(); + assert!(text.starts_with( + "--XYZ\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a_b__.txt\"\r\n" + )); + assert!(text.contains("\r\n\r\nhello\r\n--XYZ--\r\n")); + assert_eq!(header_name(" "), "file"); + assert_eq!(header_name("Schöne Grüße.pdf"), "Schöne Grüße.pdf"); + } + #[test] fn server_sent_events_come_out_whole() { let mut sse = Sse::default(); diff --git a/ui/app.css b/ui/app.css index 5548da7..f31234d 100644 --- a/ui/app.css +++ b/ui/app.css @@ -64,6 +64,8 @@ bdi { unicode-bidi: isolate; } .btn--primary { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); } .btn--primary:hover { background: var(--accent-hover); } .btn--sm { height: var(--control-h-sm); padding: 0 var(--sp-3); box-shadow: none; } +.btn--danger { color: var(--ink-inverse); background: var(--danger); border-color: var(--danger); } +.btn--danger:hover { background: var(--danger-hover); } input[type="text"], select, textarea { min-width: 0; @@ -180,8 +182,9 @@ label, .label { font: var(--text-sm) / var(--leading-tight) var(--font-ui); colo .bar__side { overflow: hidden; visibility: hidden; } .bar__agent { display: grid; grid-template-columns: var(--dot-size) auto; gap: var(--sp-2); align-items: center; padding: 0 var(--sp-3) !important; font-weight: 400 !important; } .bar__agent[data-state="running"] .dot, .bar__agent[data-state="elsewhere"] .dot { background: var(--leaf); } -.bar__agent[data-state="starting"] .dot { background: var(--accent); } -.bar__agent[data-state="failed"] .dot, .bar__agent[data-state="not_linked"] .dot, .bar__agent[data-state="no_folders"] .dot { background: var(--warning); } +.bar__agent[data-state="starting"] .dot, .bar__agent[data-state="connecting"] .dot { background: var(--accent); } +.bar__agent[data-state="failed"] .dot, .bar__agent[data-state="not_linked"] .dot, .bar__agent[data-state="no_folders"] .dot, +.bar__agent[data-state="waiting"] .dot, .bar__agent[data-state="refused"] .dot, .bar__agent[data-state="elsewhere_waiting"] .dot { background: var(--warning); } .bar__update { padding: 0 var(--sp-3) !important; color: var(--accent-ink) !important; background: var(--accent) !important; } .bar__update:hover { background: var(--accent-hover) !important; } /* Hidden keeps its grid track (auto, so zero wide): the slots after it never move. */ @@ -417,6 +420,56 @@ label, .label { font: var(--text-sm) / var(--leading-tight) var(--font-ui); colo .msg__files { margin: 0; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: var(--sp-1); font-size: var(--text-xs); } .msg__note { font-size: var(--text-sm); color: var(--ink-muted); } .msg__foot { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; } +.msg__files .chip { max-width: var(--w-chip); overflow: hidden; text-overflow: ellipsis; } +/* Under a message: small icon buttons, all one size. */ +.msg__actions { display: grid; grid-auto-flow: column; grid-auto-columns: var(--control-h-sm); justify-content: start; } +.msg--user .msg__actions { justify-content: end; margin-top: calc(-1 * var(--sp-2)); } +.msg__act { display: grid; place-items: center; width: var(--control-h-sm); height: var(--control-h-sm); padding: 0; color: var(--ink-faint); background: none; border: 0; border-radius: var(--radius); cursor: pointer; } +.msg__act:hover, .msg__act:focus-visible { color: var(--ink); background: var(--surface-hover); } +/* Regenerate: on the last reply only, and not while it is being written. */ +.msg__regen { display: none; } +.messages > .msg--assistant:last-child:not(.is-live) .msg__regen { display: grid; } +.msg__editor { display: grid; gap: var(--sp-2); } +.msg__edit { width: 100%; min-height: var(--control-h); font: var(--text-base) / var(--leading-normal) var(--font-body); resize: vertical; } +.msg__editbar { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(min(100%, 6rem), auto); gap: var(--sp-2); justify-content: end; } +.chip--model { display: inline-grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-1); align-items: center; max-width: var(--w-chip); padding-left: var(--sp-1); } +.chip--model > span:last-child { overflow: hidden; text-overflow: ellipsis; } + +/* A model's avatar: its initial on its own colour. Only the hue is the model's (models.js, the + server's stable_hue); how strong it is comes from the theme, by mixing it into the surface. */ +.avatar { + display: inline-grid; + place-items: center; + width: 1.25rem; + height: 1.25rem; + font: 700 var(--text-xs) / 1 var(--font-ui); + color: var(--ink); + background: color-mix(in oklab, hsl(var(--avatar-hue, 40) 100% 50%) 32%, var(--surface)); + border-radius: var(--radius-sm); + flex: none; +} +.avatar--none { background: var(--surface-active); } +/* The model picker: a button the height of a control, avatar · name · chevron. */ +.picker { + display: inline-grid; + grid-template-columns: auto minmax(0, 1fr) auto; + gap: var(--sp-2); + align-items: center; + height: var(--control-h); + max-width: 100%; + padding: 0 var(--control-px-sm); + font: var(--text-sm) var(--font-ui); + color: var(--ink); + text-align: left; + background: var(--bg); + border: var(--border-w) solid var(--border-strong); + border-radius: var(--radius); + cursor: pointer; +} +.picker__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.head__model { display: flex; gap: var(--sp-2); align-items: center; min-width: 0; } +.head__model .picker { height: var(--control-h-sm); max-width: var(--w-chip); } +.new .picker { width: 100%; } .msg__metrics { font: var(--text-xs) var(--font-ui); color: var(--ink-faint); } .msg__steps { display: grid; gap: var(--sp-3); min-width: 0; } .msg__status, .msg__thinking { font: var(--text-xs) var(--font-ui); } @@ -548,17 +601,57 @@ label, .label { font: var(--text-sm) / var(--leading-tight) var(--font-ui); colo .plan__phase { font-weight: 600; } .plan__task.is-done { text-decoration: line-through; color: var(--ink-muted); } -/* The composer: the text and its buttons, on two tracks; the buttons hold their own row. */ +/* The composer: the files it carries on a row of their own, then three tracks -- the attach + button, the text, the send buttons. The buttons are one control high and sit at the foot of + the row the text sets the height of: their size is known, the text's is not. */ .composer { + position: relative; display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: var(--sp-2); - align-items: end; + grid-template-columns: var(--control-h) minmax(0, 1fr) auto; + grid-template-areas: "files files files" "attach text actions"; + /* No gap between the rows: the files' row has its own margin, and none while it is empty. */ + gap: 0 var(--sp-2); padding: var(--sp-3) var(--sp-4) var(--sp-4); } +.composer[data-attach="false"] { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "files files" "text actions"; } .composer > * { min-width: 0; } -.composer textarea { width: 100%; min-height: var(--control-h); max-height: 40vh; font: var(--text-base) / var(--leading-normal) var(--font-body); } -.composer__actions { display: grid; grid-auto-flow: column; gap: var(--sp-2); } +.composer textarea { grid-area: text; width: 100%; min-height: var(--control-h); max-height: 40vh; font: var(--text-base) / var(--leading-normal) var(--font-body); } +.composer__attach { grid-area: attach; align-self: end; width: var(--control-h); padding: 0; } +.composer__actions { grid-area: actions; align-self: end; display: grid; grid-auto-flow: column; gap: var(--sp-2); } +.composer__files { grid-area: files; margin: 0 0 var(--sp-2); padding: 0; list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--w-chip)), 1fr)); gap: var(--sp-2); } +/* One file: name · size or state · remove, the same tracks on every row. */ +.file { + display: grid; + grid-template-columns: minmax(0, 1fr) auto var(--control-h-sm); + gap: var(--sp-2); + align-items: center; + height: var(--control-h-sm); + padding-left: var(--sp-2); + font: var(--text-xs) var(--font-ui); + background: var(--surface); + border: var(--border-w) solid var(--border); + border-radius: var(--radius); +} +.file__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ink); } +.file__state { color: var(--ink-muted); max-width: var(--w-chip); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.file.is-failed { border-color: var(--warning); } +.file.is-failed .file__state { color: var(--warning); } +.file__remove { display: grid; place-items: center; height: 100%; padding: 0; color: var(--ink-muted); background: none; border: 0; border-left: var(--border-w) solid var(--border); cursor: pointer; } +.file__remove:hover { color: var(--ink); background: var(--surface-hover); } +/* Files dragged over the window: where they will go, over the whole composer. */ +.composer__drop { display: none; } +.composer[data-drag="true"] .composer__drop { + display: grid; + place-items: center; + position: absolute; + inset: var(--sp-2) var(--sp-4); + z-index: var(--z-raised); + font: 600 var(--text-sm) var(--font-ui); + color: var(--accent); + background: var(--accent-soft); + border: var(--border-w-thick) dashed var(--accent); + border-radius: var(--radius); +} /* --- Settings ------------------------------------------------------------------------------ */ @@ -625,6 +718,83 @@ label, .label { font: var(--text-sm) / var(--leading-tight) var(--font-ui); colo .viewer__note { padding: var(--sp-4); font-size: var(--text-sm); color: var(--ink-muted); } .viewer__note.is-error { color: var(--warning); } +/* --- Menus, dialogs and the palette ----------------------------------------------------------- */ + +.menu { + position: fixed; + z-index: var(--z-dropdown); + min-width: 12rem; + max-width: min(var(--w-menu), calc(100vw - 2 * var(--sp-2))); + max-height: min(var(--h-list), calc(100vh - 2 * var(--sp-2))); + overflow: auto; + padding: var(--sp-1) 0; + background: var(--surface-raised); + border: var(--border-w) solid var(--border-strong); + border-radius: var(--radius); + box-shadow: var(--shadow-lg); +} +/* Every item: lead · label · check, on the same tracks, so labels line up with or without a lead. */ +.menu__item { + display: grid; + grid-template-columns: 1.25rem minmax(0, 1fr) 1rem; + gap: var(--sp-2); + align-items: center; + width: 100%; + min-height: var(--row-h); + padding: 0 var(--sp-3); + font: var(--text-sm) var(--font-ui); + color: var(--ink); + text-align: left; + background: none; + border: 0; + cursor: pointer; +} +/* A menu where no item has a lead: no lead track either. */ +.menu--plain .menu__item { grid-template-columns: minmax(0, 1fr) 1rem; } +.menu--plain .menu__lead { display: none; } +.menu__item:hover, .menu__item:focus-visible { background: var(--surface-hover); outline: none; } +.menu__item:disabled { opacity: var(--opacity-dim); cursor: default; } +.menu__item.is-danger { color: var(--danger); } +.menu__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.menu__check { color: var(--accent); } +.menu__sep { height: var(--border-w); margin: var(--sp-1) 0; background: var(--border); } + +.dialog { + width: min(var(--w-dialog), calc(100vw - 2 * var(--sp-4))); + padding: 0; + color: var(--ink); + background: var(--surface-raised); + border: var(--border-w) solid var(--border-strong); + border-radius: var(--radius); + box-shadow: var(--shadow-lg); +} +.dialog::backdrop { background: var(--scrim); } +.dialog__body { display: grid; gap: var(--sp-3); padding: var(--sp-4); } +.dialog__title { font: 600 var(--text-md) / var(--leading-tight) var(--font-ui); } +.dialog__input { width: 100%; } +.dialog__actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2); } + +.palette { align-self: start; margin-top: 12vh; } +.palette__line { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-2); align-items: center; padding: 0 var(--sp-3); border-bottom: var(--border-w) solid var(--border); color: var(--ink-muted); } +.palette__input { height: var(--control-h-lg) !important; border: 0 !important; background: none !important; outline: none; font-size: var(--text-base); } +.palette__list { list-style: none; margin: 0; padding: var(--sp-1) 0; max-height: var(--h-list); overflow: auto; } +/* A result: icon · label · hint, the same tracks on every row. */ +.palette__item { + display: grid; + grid-template-columns: 1rem minmax(0, 1fr) auto; + gap: var(--sp-3); + align-items: center; + min-height: var(--row-h); + padding: 0 var(--sp-3); + font: var(--text-sm) var(--font-ui); + cursor: pointer; +} +.palette__item[aria-selected="true"] { background: var(--surface-active); } +.palette__icon { display: grid; place-items: center; color: var(--ink-muted); } +.palette__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } +.palette__hint { font-size: var(--text-xs); color: var(--ink-faint); white-space: nowrap; } +.palette__none { padding: var(--sp-3); font-size: var(--text-sm); color: var(--ink-muted); } + /* --- Narrower windows ---------------------------------------------------------------------- */ /* 64rem (--bp-wide): the viewer goes over the chat instead of beside it. */ @@ -651,13 +821,19 @@ label, .label { font: var(--text-sm) / var(--leading-tight) var(--font-ui); colo .shell[data-side="true"] .side { transform: none; } .shell[data-side="true"] .scrim { display: block; position: absolute; inset: 0; z-index: calc(var(--z-panel) - 1); background: var(--scrim); } .viewer { z-index: var(--z-panel); } - .head { padding: 0 var(--sp-3); grid-template-columns: minmax(0, 1fr) auto; } + .head { padding: var(--sp-2) var(--sp-3) 0; grid-template-columns: minmax(0, 1fr) auto; } .head__place { grid-column: 1 / -1; grid-row: 2; padding-bottom: var(--sp-2); } + /* The chat's controls get a row of their own: beside the title they would squeeze it out. */ + .head__tools { grid-column: 1 / -1; grid-row: 3; flex-wrap: wrap; padding-bottom: var(--sp-2); } + .head__tools:empty { display: none; } .thread { padding: var(--sp-3); } .composer { padding: var(--sp-2) var(--sp-3) var(--sp-3); } .cards { padding: var(--sp-2) var(--sp-3) 0; } .card { padding: var(--sp-4); gap: var(--sp-4); } .msg--user { max-width: 100%; } + .palette__hint { display: none; } + .palette__item { grid-template-columns: 1rem minmax(0, 1fr); } + .head__model .picker { max-width: 8rem; } } /* Very narrow: the agent's words go, its dot stays; the row's button goes under its field. */ diff --git a/ui/i18n.js b/ui/i18n.js index 2dd9e2c..81c1e45 100644 --- a/ui/i18n.js +++ b/ui/i18n.js @@ -13,6 +13,7 @@ const LEMBAS_I18N = { "cs": { "%(n)s tokens": "tokeny: %(n)s", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Služba LLeMbas CLI už tu běží (pid %(pid)s), ale není připojená: %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Služba LLeMbas CLI už tu běží (pid %(pid)s); je agentem tohoto počítače.", "A chat asking for more is lowered to this.": "Konverzace, která chce víc, je snížena na tohle.", "A chat asking for more than the folder allows is lowered to it.": "Konverzace, která chce víc, než složka dovoluje, je snížena na to.", @@ -22,13 +23,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "Adresa vašeho serveru LLeMbas", "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 stopped: %(why)s": "Agent se zastavil: %(why)s", + "Agent: %(why)s": "Agent: %(why)s", "Allow": "Povolit", "Always allow": "Vždy povolit", "An agent chat works in a folder on this computer, through the agent built into this app.": "Agentní konverzace pracuje ve složce na tomto počítači přes agenta vestavěného v této aplikaci.", "Another device": "Jiné zařízení", "Answer": "Odpovědět", "Approve this code in the page that opened in your browser:": "Potvrďte tento kód na stránce, která se otevřela ve vašem prohlížeči:", + "Archive chat": "Archivovat konverzaci", + "Attach files": "Přikládat soubory", "Auto — asks nothing": "Auto — na nic se neptá", "Cancel": "Zrušit", "Carry it out": "Provést to", @@ -37,11 +40,17 @@ const LEMBAS_I18N = { "Close": "Zavřít", "Close (LLeMbas stays in the tray)": "Zavřít (LLeMbas zůstane v oznamovací oblasti)", "Command": "Příkaz", + "Commands": "Příkazy", "Connect": "Připojit", "Copy": "Kopírovat", "Cut short here; the whole of it is in the web interface.": "Zde zkráceno; celé je to ve webovém rozhraní.", + "Delete": "Smazat", + "Delete chat": "Smazat konverzaci", + "Delete this chat?": "Smazat tuto konverzaci?", "Deny": "Zamítnout", + "Drop the files to attach them": "Pusťte soubory a přiloží se", "Earlier messages": "Starší zprávy", + "Edit message": "Upravit zprávu", "Edit — asks before commands": "Edit — ptá se před příkazy", "Empty folder": "Prázdná složka", "Folder": "Složka", @@ -56,6 +65,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — nejdřív se zeptá", "Maximise": "Maximalizovat", "Menu": "Nabídka", + "Message": "Zpráva", "Minimise": "Minimalizovat", "Mode": "Režim", "Model": "Model", @@ -69,9 +79,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Tomuto účtu není dostupný žádný model.", "No project folder yet: the agent can open nothing on this computer.": "Zatím žádná složka projektu: agent na tomto počítači nemůže nic otevřít.", "Not yet": "Ještě ne", + "Nothing matches.": "Nic neodpovídá.", "Notifications": "Upozornění", "Only the agent's folders: Settings, Project folders.": "Jen složky agenta: Nastavení, Složky projektů.", "Open in the viewer": "Otevřít v prohlížeči souborů", + "Open in the web interface": "Otevřít ve webovém rozhraní", "Open the page again": "Otevřít stránku znovu", "Or type an answer": "Nebo napište odpověď", "Other devices": "Jiná zařízení", @@ -80,12 +92,22 @@ const LEMBAS_I18N = { "Preview": "Náhled", "Project folders": "Složky projektů", "Read again": "Načíst znovu", + "Reasoning effort": "Náročnost uvažování", "Recommended": "Doporučeno", + "Regenerate reply": "Vygenerovat odpověď znovu", "Remove": "Odebrat", "Remove %(dir)s": "Odebrat %(dir)s", + "Rename": "Přejmenovat", + "Rename chat": "Přejmenovat konverzaci", + "Rename…": "Přejmenovat…", "Restore": "Obnovit", + "Save": "Uložit", + "Saving starts the reply again; everything after this message is dropped.": "Uložením se odpověď spustí znovu; vše po této zprávě se zahodí.", + "Search": "Hledat", "Send": "Odeslat", + "Sending…": "Odesílá se…", "Server": "Server", + "Server files": "Soubory na serveru", "Settings": "Nastavení", "Shown by the agent": "Ukázal agent", "Sign in": "Přihlásit se", @@ -103,12 +125,15 @@ const LEMBAS_I18N = { "The agent asks:": "Agent se ptá:", "The agent has finished.": "Agent dokončil práci.", "The agent is connected, but may open no folder yet: add one below.": "Agent je připojený, ale zatím nesmí otevřít žádnou složku: přidejte ji níže.", + "The agent is connecting to the server…": "Agent se připojuje k serveru…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "Agent ještě není připojený k serveru: spusťte ho nebo propojte tento počítač v Nastavení.", + "The agent is not connected: %(why)s. It keeps trying.": "Agent není připojený: %(why)s. Zkouší to dál.", "The agent is off.": "Agent je vypnutý.", "The agent is running: agent chats can use this computer.": "Agent běží: agentní konverzace mohou používat tento počítač.", "The agent is starting…": "Agent se spouští…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "Agent umožňuje agentním konverzacím na vašem serveru pracovat v projektech na tomto počítači. Je to vestavěné LLeMbas CLI, které běží na pozadí, dokud běží aplikace.", "The agent stopped: %(why)s. Trying again shortly.": "Agent se zastavil: %(why)s. Za chvíli to zkusí znovu.", + "The agent will not connect: %(why)s. Link this computer again.": "Agent se nepřipojí: %(why)s. Propojte tento počítač znovu.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "Aplikace se k vašemu serveru přihlásí jednou, ve vašem prohlížeči, jako každé zařízení. Pak může číst a psát vaše konverzace, jako webové rozhraní, a stejné schválení propojí tento počítač pro agenta.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "Aplikace se k vašemu serveru přihlásí jednou, ve vašem prohlížeči, jako každé zařízení. Pak může číst a psát vaše konverzace, jako webové rozhraní.", "The connection to the reply was lost: %(why)s": "Spojení s odpovědí se přerušilo: %(why)s", @@ -118,27 +143,36 @@ const LEMBAS_I18N = { "This computer": "Tento počítač", "This computer is not linked to the server yet.": "Tento počítač ještě není propojený se serverem.", "This file is too big to show here (%(size)s).": "Tento soubor je příliš velký na zobrazení (%(size)s).", + "This file is too large to send.": "Tento soubor je příliš velký na odeslání.", + "This is a folder; attach the files in it.": "Toto je složka; přiložte soubory v ní.", "This is not a text file; it cannot be shown here.": "Toto není textový soubor; nelze ho zde zobrazit.", "Thought": "Přemýšlel", "Try again": "Zkusit znovu", + "Type a command or a chat's title": "Napište příkaz nebo název konverzace", + "Wait until the files are sent.": "Počkejte, až se soubory odešlou.", "Waiting for the browser…": "Čeká se na prohlížeč…", "Waiting for the reply before it": "Čeká na odpověď před ní", "Waiting for your answer": "Čeká na vaši odpověď", "Writing…": "Píše se…", "Your answer": "Vaše odpověď", + "agent connecting…": "agent se připojuje…", "agent off": "agent vypnutý", + "agent refused": "agent odmítnut", "agent running": "agent běží", "agent running (service)": "agent běží (služba)", "agent starting…": "agent se spouští…", "agent stopped": "agent zastaven", + "agent waiting": "agent čeká", "agent: no folders": "agent: žádné složky", "not linked": "nepropojeno", "update": "aktualizace", - "update %(version)s": "aktualizace %(version)s" + "update %(version)s": "aktualizace %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s“ se smaže na serveru i se svými zprávami a soubory. Nelze to vrátit." }, "de": { "%(n)s tokens": "%(n)s Tokens", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Ein LLeMbas-CLI-Dienst läuft hier bereits (PID %(pid)s), ist aber nicht verbunden: %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Ein LLeMbas-CLI-Dienst läuft hier bereits (PID %(pid)s); er ist der Agent dieses Computers.", "A chat asking for more is lowered to this.": "Ein Chat, der mehr verlangt, wird hierauf begrenzt.", "A chat asking for more than the folder allows is lowered to it.": "Ein Chat, der mehr will, als der Ordner erlaubt, wird darauf herabgesetzt.", @@ -148,13 +182,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "Adresse Ihres LLeMbas-Servers", "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 stopped: %(why)s": "Agent angehalten: %(why)s", + "Agent: %(why)s": "Agent: %(why)s", "Allow": "Erlauben", "Always allow": "Immer erlauben", "An agent chat works in a folder on this computer, through the agent built into this app.": "Ein Agenten-Chat arbeitet in einem Ordner auf diesem Computer, über den in diese App eingebauten Agenten.", "Another device": "Ein anderes Gerät", "Answer": "Antworten", "Approve this code in the page that opened in your browser:": "Bestätigen Sie diesen Code auf der Seite, die sich in Ihrem Browser geöffnet hat:", + "Archive chat": "Chat archivieren", + "Attach files": "Dateien anhängen", "Auto — asks nothing": "Auto — fragt nichts", "Cancel": "Abbrechen", "Carry it out": "Ausführen", @@ -163,11 +199,17 @@ const LEMBAS_I18N = { "Close": "Schließen", "Close (LLeMbas stays in the tray)": "Schließen (LLeMbas bleibt im Benachrichtigungsbereich)", "Command": "Befehl", + "Commands": "Befehle", "Connect": "Verbinden", "Copy": "Kopieren", "Cut short here; the whole of it is in the web interface.": "Hier gekürzt; vollständig steht es in der Weboberfläche.", + "Delete": "Löschen", + "Delete chat": "Chat löschen", + "Delete this chat?": "Diesen Chat löschen?", "Deny": "Ablehnen", + "Drop the files to attach them": "Dateien ablegen, um sie anzuhängen", "Earlier messages": "Frühere Nachrichten", + "Edit message": "Nachricht bearbeiten", "Edit — asks before commands": "Edit — fragt vor Befehlen", "Empty folder": "Leerer Ordner", "Folder": "Ordner", @@ -182,6 +224,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — fragt zuerst", "Maximise": "Maximieren", "Menu": "Menü", + "Message": "Nachricht", "Minimise": "Minimieren", "Mode": "Modus", "Model": "Modell", @@ -195,9 +238,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Diesem Konto steht kein Modell zur Verfügung.", "No project folder yet: the agent can open nothing on this computer.": "Noch kein Projektordner: Der Agent kann auf diesem Computer nichts öffnen.", "Not yet": "Noch nicht", + "Nothing matches.": "Nichts gefunden.", "Notifications": "Benachrichtigungen", "Only the agent's folders: Settings, Project folders.": "Nur die Ordner des Agenten: Einstellungen, Projektordner.", "Open in the viewer": "Im Betrachter öffnen", + "Open in the web interface": "In der Weboberfläche öffnen", "Open the page again": "Seite erneut öffnen", "Or type an answer": "Oder eine Antwort eingeben", "Other devices": "Andere Geräte", @@ -206,12 +251,22 @@ const LEMBAS_I18N = { "Preview": "Vorschau", "Project folders": "Projektordner", "Read again": "Neu lesen", + "Reasoning effort": "Denkaufwand", "Recommended": "Empfohlen", + "Regenerate reply": "Antwort neu generieren", "Remove": "Entfernen", "Remove %(dir)s": "%(dir)s entfernen", + "Rename": "Umbenennen", + "Rename chat": "Chat umbenennen", + "Rename…": "Umbenennen…", "Restore": "Wiederherstellen", + "Save": "Speichern", + "Saving starts the reply again; everything after this message is dropped.": "Speichern startet die Antwort neu; alles nach dieser Nachricht wird verworfen.", + "Search": "Suchen", "Send": "Senden", + "Sending…": "Wird gesendet…", "Server": "Server", + "Server files": "Dateien auf dem Server", "Settings": "Einstellungen", "Shown by the agent": "Vom Agenten gezeigt", "Sign in": "Anmelden", @@ -229,12 +284,15 @@ const LEMBAS_I18N = { "The agent asks:": "Der Agent fragt:", "The agent has finished.": "Der Agent ist fertig.", "The agent is connected, but may open no folder yet: add one below.": "Der Agent ist verbunden, darf aber noch keinen Ordner öffnen: Fügen Sie unten einen hinzu.", + "The agent is connecting to the server…": "Der Agent verbindet sich mit dem Server…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "Der Agent ist noch nicht mit dem Server verbunden: Starten Sie ihn oder verbinden Sie diesen Computer unter Einstellungen.", + "The agent is not connected: %(why)s. It keeps trying.": "Der Agent ist nicht verbunden: %(why)s. Er versucht es weiter.", "The agent is off.": "Der Agent ist aus.", "The agent is running: agent chats can use this computer.": "Der Agent läuft: Agent-Chats können diesen Computer nutzen.", "The agent is starting…": "Der Agent startet…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "Der Agent lässt Agent-Chats auf Ihrem Server in den Projekten dieses Computers arbeiten. Er ist das eingebaute LLeMbas CLI und läuft im Hintergrund, solange die App läuft.", "The agent stopped: %(why)s. Trying again shortly.": "Der Agent hat angehalten: %(why)s. Gleich wird es erneut versucht.", + "The agent will not connect: %(why)s. Link this computer again.": "Der Agent verbindet sich nicht: %(why)s. Verknüpfen Sie diesen Computer erneut.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "Die App meldet sich einmal an Ihrem Server an, in Ihrem Browser, wie jedes Gerät. Danach kann sie Ihre Chats lesen und schreiben, wie die Weboberfläche, und dieselbe Freigabe verbindet diesen Computer für den Agenten.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "Die App meldet sich einmal an Ihrem Server an, in Ihrem Browser, wie jedes Gerät. Danach kann sie Ihre Chats lesen und schreiben, wie die Weboberfläche.", "The connection to the reply was lost: %(why)s": "Die Verbindung zur Antwort ist abgebrochen: %(why)s", @@ -244,27 +302,36 @@ const LEMBAS_I18N = { "This computer": "Dieser Computer", "This computer is not linked to the server yet.": "Dieser Computer ist noch nicht mit dem Server verbunden.", "This file is too big to show here (%(size)s).": "Diese Datei ist zu groß für die Anzeige (%(size)s).", + "This file is too large to send.": "Diese Datei ist zu groß zum Senden.", + "This is a folder; attach the files in it.": "Das ist ein Ordner; hängen Sie die Dateien darin an.", "This is not a text file; it cannot be shown here.": "Das ist keine Textdatei; sie kann hier nicht angezeigt werden.", "Thought": "Nachgedacht", "Try again": "Erneut versuchen", + "Type a command or a chat's title": "Befehl oder Chat-Titel eingeben", + "Wait until the files are sent.": "Warten Sie, bis die Dateien gesendet sind.", "Waiting for the browser…": "Warte auf den Browser…", "Waiting for the reply before it": "Wartet auf die Antwort davor", "Waiting for your answer": "Wartet auf Ihre Antwort", "Writing…": "Schreibt…", "Your answer": "Ihre Antwort", + "agent connecting…": "Agent verbindet…", "agent off": "Agent aus", + "agent refused": "Agent abgewiesen", "agent running": "Agent läuft", "agent running (service)": "Agent läuft (Dienst)", "agent starting…": "Agent startet…", "agent stopped": "Agent angehalten", + "agent waiting": "Agent wartet", "agent: no folders": "Agent: keine Ordner", "not linked": "nicht verbunden", "update": "Update", - "update %(version)s": "Update %(version)s" + "update %(version)s": "Update %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s“ wird auf dem Server gelöscht, mit seinen Nachrichten und Dateien. Das lässt sich nicht rückgängig machen." }, "es": { "%(n)s tokens": "tokens: %(n)s", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Ya hay un servicio de LLeMbas CLI aquí (pid %(pid)s), pero no está conectado: %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Ya se está ejecutando aquí un servicio de LLeMbas CLI (pid %(pid)s); es el agente de este ordenador.", "A chat asking for more is lowered to this.": "Un chat que pida más se limita a esto.", "A chat asking for more than the folder allows is lowered to it.": "Un chat que pide más de lo que la carpeta permite se rebaja a ello.", @@ -274,13 +341,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "Dirección de su servidor LLeMbas", "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 stopped: %(why)s": "El agente se detuvo: %(why)s", + "Agent: %(why)s": "Agente: %(why)s", "Allow": "Permitir", "Always allow": "Permitir siempre", "An agent chat works in a folder on this computer, through the agent built into this app.": "Un chat de agente trabaja en una carpeta de este equipo, a través del agente integrado en esta aplicación.", "Another device": "Otro dispositivo", "Answer": "Responder", "Approve this code in the page that opened in your browser:": "Apruebe este código en la página que se abrió en su navegador:", + "Archive chat": "Archivar chat", + "Attach files": "Adjuntar archivos", "Auto — asks nothing": "Auto — no pregunta nada", "Cancel": "Cancelar", "Carry it out": "Llevarlo a cabo", @@ -289,11 +358,17 @@ const LEMBAS_I18N = { "Close": "Cerrar", "Close (LLeMbas stays in the tray)": "Cerrar (LLeMbas sigue en la bandeja del sistema)", "Command": "Comando", + "Commands": "Comandos", "Connect": "Conectar", "Copy": "Copiar", "Cut short here; the whole of it is in the web interface.": "Recortado aquí; completo está en la interfaz web.", + "Delete": "Eliminar", + "Delete chat": "Eliminar chat", + "Delete this chat?": "¿Eliminar este chat?", "Deny": "Denegar", + "Drop the files to attach them": "Suelte los archivos para adjuntarlos", "Earlier messages": "Mensajes anteriores", + "Edit message": "Editar mensaje", "Edit — asks before commands": "Edit — pregunta antes de los comandos", "Empty folder": "Carpeta vacía", "Folder": "Carpeta", @@ -308,6 +383,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — pregunta primero", "Maximise": "Maximizar", "Menu": "Menú", + "Message": "Mensaje", "Minimise": "Minimizar", "Mode": "Modo", "Model": "Modelo", @@ -321,9 +397,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Ningún modelo está disponible para esta cuenta.", "No project folder yet: the agent can open nothing on this computer.": "Aún no hay carpeta de proyecto: el agente no puede abrir nada en este equipo.", "Not yet": "Todavía no", + "Nothing matches.": "No hay coincidencias.", "Notifications": "Notificaciones", "Only the agent's folders: Settings, Project folders.": "Solo las carpetas del agente: Ajustes, Carpetas de proyecto.", "Open in the viewer": "Abrir en el visor", + "Open in the web interface": "Abrir en la interfaz web", "Open the page again": "Abrir la página de nuevo", "Or type an answer": "O escriba una respuesta", "Other devices": "Otros dispositivos", @@ -332,12 +410,22 @@ const LEMBAS_I18N = { "Preview": "Vista previa", "Project folders": "Carpetas de proyectos", "Read again": "Leer de nuevo", + "Reasoning effort": "Esfuerzo de razonamiento", "Recommended": "Recomendado", + "Regenerate reply": "Regenerar respuesta", "Remove": "Quitar", "Remove %(dir)s": "Quitar %(dir)s", + "Rename": "Renombrar", + "Rename chat": "Renombrar chat", + "Rename…": "Renombrar…", "Restore": "Restaurar", + "Save": "Guardar", + "Saving starts the reply again; everything after this message is dropped.": "Al guardar, la respuesta empieza de nuevo; se descarta todo lo que hay después de este mensaje.", + "Search": "Buscar", "Send": "Enviar", + "Sending…": "Enviando…", "Server": "Servidor", + "Server files": "Archivos del servidor", "Settings": "Ajustes", "Shown by the agent": "Mostrado por el agente", "Sign in": "Iniciar sesión", @@ -355,12 +443,15 @@ const LEMBAS_I18N = { "The agent asks:": "El agente pregunta:", "The agent has finished.": "El agente ha terminado.", "The agent is connected, but may open no folder yet: add one below.": "El agente está conectado, pero aún no puede abrir ninguna carpeta: añada una abajo.", + "The agent is connecting to the server…": "El agente se está conectando al servidor…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "El agente aún no está conectado al servidor: inícielo o vincule este equipo en Ajustes.", + "The agent is not connected: %(why)s. It keeps trying.": "El agente no está conectado: %(why)s. Sigue intentándolo.", "The agent is off.": "El agente está apagado.", "The agent is running: agent chats can use this computer.": "El agente está en marcha: los chats de agente pueden usar este ordenador.", "The agent is starting…": "El agente se está iniciando…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "El agente permite que los chats de agente de su servidor trabajen en los proyectos de este ordenador. Es el LLeMbas CLI, integrado, que se ejecuta en segundo plano mientras la aplicación está abierta.", "The agent stopped: %(why)s. Trying again shortly.": "El agente se detuvo: %(why)s. Se volverá a intentar en breve.", + "The agent will not connect: %(why)s. Link this computer again.": "El agente no se conectará: %(why)s. Vuelva a vincular este equipo.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "La aplicación inicia sesión en su servidor una vez, en su navegador, como cualquier dispositivo. Después puede leer y escribir sus chats, como la interfaz web, y la misma aprobación vincula este ordenador para el agente.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "La aplicación inicia sesión en su servidor una vez, en su navegador, como cualquier dispositivo. Después puede leer y escribir sus chats, como la interfaz web.", "The connection to the reply was lost: %(why)s": "Se perdió la conexión con la respuesta: %(why)s", @@ -370,27 +461,36 @@ const LEMBAS_I18N = { "This computer": "Este equipo", "This computer is not linked to the server yet.": "Este ordenador aún no está vinculado al servidor.", "This file is too big to show here (%(size)s).": "Este archivo es demasiado grande para mostrarlo (%(size)s).", + "This file is too large to send.": "Este archivo es demasiado grande para enviarlo.", + "This is a folder; attach the files in it.": "Esto es una carpeta; adjunte los archivos que contiene.", "This is not a text file; it cannot be shown here.": "No es un archivo de texto; no se puede mostrar aquí.", "Thought": "Pensamiento", "Try again": "Reintentar", + "Type a command or a chat's title": "Escriba un comando o el título de un chat", + "Wait until the files are sent.": "Espere a que se envíen los archivos.", "Waiting for the browser…": "Esperando al navegador…", "Waiting for the reply before it": "Esperando la respuesta anterior", "Waiting for your answer": "Esperando su respuesta", "Writing…": "Escribiendo…", "Your answer": "Su respuesta", + "agent connecting…": "agente conectando…", "agent off": "agente apagado", + "agent refused": "agente rechazado", "agent running": "agente en marcha", "agent running (service)": "agente en marcha (servicio)", "agent starting…": "agente iniciando…", "agent stopped": "agente detenido", + "agent waiting": "agente en espera", "agent: no folders": "agente: sin carpetas", "not linked": "no vinculado", "update": "actualizar", - "update %(version)s": "actualizar a %(version)s" + "update %(version)s": "actualizar a %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "«%(title)s» se elimina del servidor, con sus mensajes y archivos. No se puede deshacer." }, "fr": { "%(n)s tokens": "%(n)s jetons", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Un service LLeMbas CLI tourne déjà ici (pid %(pid)s), mais n'est pas connecté : %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Un service LLeMbas CLI tourne déjà ici (pid %(pid)s) ; c'est l'agent de cet ordinateur.", "A chat asking for more is lowered to this.": "Une conversation qui en demande plus est ramenée à ceci.", "A chat asking for more than the folder allows is lowered to it.": "Une discussion qui demande plus que le dossier ne permet y est ramenée.", @@ -400,13 +500,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "Adresse de votre serveur LLeMbas", "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 stopped: %(why)s": "L'agent s'est arrêté : %(why)s", + "Agent: %(why)s": "Agent : %(why)s", "Allow": "Autoriser", "Always allow": "Toujours autoriser", "An agent chat works in a folder on this computer, through the agent built into this app.": "Une discussion d'agent travaille dans un dossier de cet ordinateur, via l'agent intégré à cette application.", "Another device": "Un autre appareil", "Answer": "Répondre", "Approve this code in the page that opened in your browser:": "Approuvez ce code sur la page qui s'est ouverte dans votre navigateur :", + "Archive chat": "Archiver la conversation", + "Attach files": "Joindre des fichiers", "Auto — asks nothing": "Auto — ne demande rien", "Cancel": "Annuler", "Carry it out": "Exécuter", @@ -415,11 +517,17 @@ const LEMBAS_I18N = { "Close": "Fermer", "Close (LLeMbas stays in the tray)": "Fermer (LLeMbas reste dans la zone de notification)", "Command": "Commande", + "Commands": "Commandes", "Connect": "Se connecter", "Copy": "Copier", "Cut short here; the whole of it is in the web interface.": "Tronqué ici ; le tout est dans l'interface web.", + "Delete": "Supprimer", + "Delete chat": "Supprimer la conversation", + "Delete this chat?": "Supprimer cette conversation ?", "Deny": "Refuser", + "Drop the files to attach them": "Déposez les fichiers pour les joindre", "Earlier messages": "Messages précédents", + "Edit message": "Modifier le message", "Edit — asks before commands": "Edit — demande avant les commandes", "Empty folder": "Dossier vide", "Folder": "Dossier", @@ -434,6 +542,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — demande d'abord", "Maximise": "Agrandir", "Menu": "Menu", + "Message": "Message", "Minimise": "Réduire", "Mode": "Mode", "Model": "Modèle", @@ -447,9 +556,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Aucun modèle n'est disponible pour ce compte.", "No project folder yet: the agent can open nothing on this computer.": "Aucun dossier de projet pour l'instant : l'agent ne peut rien ouvrir sur cet ordinateur.", "Not yet": "Pas encore", + "Nothing matches.": "Aucun résultat.", "Notifications": "Notifications", "Only the agent's folders: Settings, Project folders.": "Uniquement les dossiers de l'agent : Paramètres, Dossiers de projet.", "Open in the viewer": "Ouvrir dans la visionneuse", + "Open in the web interface": "Ouvrir dans l'interface web", "Open the page again": "Rouvrir la page", "Or type an answer": "Ou saisissez une réponse", "Other devices": "Autres appareils", @@ -458,12 +569,22 @@ const LEMBAS_I18N = { "Preview": "Aperçu", "Project folders": "Dossiers de projets", "Read again": "Relire", + "Reasoning effort": "Effort de raisonnement", "Recommended": "Recommandé", + "Regenerate reply": "Régénérer la réponse", "Remove": "Retirer", "Remove %(dir)s": "Retirer %(dir)s", + "Rename": "Renommer", + "Rename chat": "Renommer la conversation", + "Rename…": "Renommer…", "Restore": "Restaurer", + "Save": "Enregistrer", + "Saving starts the reply again; everything after this message is dropped.": "Enregistrer relance la réponse ; tout ce qui suit ce message est supprimé.", + "Search": "Rechercher", "Send": "Envoyer", + "Sending…": "Envoi…", "Server": "Serveur", + "Server files": "Fichiers du serveur", "Settings": "Paramètres", "Shown by the agent": "Montré par l'agent", "Sign in": "Se connecter", @@ -481,12 +602,15 @@ const LEMBAS_I18N = { "The agent asks:": "L'agent demande :", "The agent has finished.": "L'agent a terminé.", "The agent is connected, but may open no folder yet: add one below.": "L'agent est connecté, mais ne peut encore ouvrir aucun dossier : ajoutez-en un ci-dessous.", + "The agent is connecting to the server…": "L'agent se connecte au serveur…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "L'agent n'est pas encore connecté au serveur : démarrez-le ou reliez cet ordinateur dans les Paramètres.", + "The agent is not connected: %(why)s. It keeps trying.": "L'agent n'est pas connecté : %(why)s. Il réessaie.", "The agent is off.": "L'agent est arrêté.", "The agent is running: agent chats can use this computer.": "L'agent tourne : les conversations d'agent peuvent utiliser cet ordinateur.", "The agent is starting…": "L'agent démarre…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "L'agent permet aux conversations d'agent de votre serveur de travailler dans les projets de cet ordinateur. C'est le LLeMbas CLI, intégré, qui tourne en arrière-plan tant que l'application est ouverte.", "The agent stopped: %(why)s. Trying again shortly.": "L'agent s'est arrêté : %(why)s. Nouvel essai sous peu.", + "The agent will not connect: %(why)s. Link this computer again.": "L'agent ne se connectera pas : %(why)s. Reliez à nouveau cet ordinateur.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "L'application se connecte une fois à votre serveur, dans votre navigateur, comme tout appareil. Elle peut ensuite lire et écrire vos discussions, comme l'interface web, et la même approbation relie cet ordinateur pour l'agent.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "L'application se connecte une fois à votre serveur, dans votre navigateur, comme tout appareil. Elle peut ensuite lire et écrire vos discussions, comme l'interface web.", "The connection to the reply was lost: %(why)s": "La connexion à la réponse a été perdue : %(why)s", @@ -496,27 +620,36 @@ const LEMBAS_I18N = { "This computer": "Cet ordinateur", "This computer is not linked to the server yet.": "Cet ordinateur n'est pas encore relié au serveur.", "This file is too big to show here (%(size)s).": "Ce fichier est trop volumineux pour être affiché (%(size)s).", + "This file is too large to send.": "Ce fichier est trop volumineux pour être envoyé.", + "This is a folder; attach the files in it.": "C'est un dossier ; joignez les fichiers qu'il contient.", "This is not a text file; it cannot be shown here.": "Ce n'est pas un fichier texte ; il ne peut pas être affiché ici.", "Thought": "Réflexion", "Try again": "Réessayer", + "Type a command or a chat's title": "Tapez une commande ou le titre d'une conversation", + "Wait until the files are sent.": "Attendez que les fichiers soient envoyés.", "Waiting for the browser…": "En attente du navigateur…", "Waiting for the reply before it": "En attente de la réponse précédente", "Waiting for your answer": "En attente de votre réponse", "Writing…": "Rédaction…", "Your answer": "Votre réponse", + "agent connecting…": "agent en connexion…", "agent off": "agent arrêté", + "agent refused": "agent refusé", "agent running": "agent actif", "agent running (service)": "agent actif (service)", "agent starting…": "agent en démarrage…", "agent stopped": "agent interrompu", + "agent waiting": "agent en attente", "agent: no folders": "agent : aucun dossier", "not linked": "non relié", "update": "mise à jour", - "update %(version)s": "mise à jour %(version)s" + "update %(version)s": "mise à jour %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "« %(title)s » est supprimée du serveur, avec ses messages et ses fichiers. C'est définitif." }, "hu": { "%(n)s tokens": "%(n)s token", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Egy LLeMbas CLI szolgáltatás már fut itt (pid %(pid)s), de nincs csatlakozva: %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Itt már fut egy LLeMbas CLI szolgáltatás (pid %(pid)s); ez a számítógép ügynöke.", "A chat asking for more is lowered to this.": "Az ennél többet kérő csevegés erre korlátozódik.", "A chat asking for more than the folder allows is lowered to it.": "A csevegés, amely többet kér, mint amit a mappa enged, erre lesz csökkentve.", @@ -526,13 +659,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "A LLeMbas-kiszolgáló címe", "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 stopped: %(why)s": "Az ügynök leállt: %(why)s", + "Agent: %(why)s": "Ügynök: %(why)s", "Allow": "Engedélyezés", "Always allow": "Mindig engedélyez", "An agent chat works in a folder on this computer, through the agent built into this app.": "Az ügynökcsevegés egy mappában dolgozik ezen a számítógépen, az alkalmazásba épített ügynökön keresztül.", "Another device": "Másik eszköz", "Answer": "Válasz", "Approve this code in the page that opened in your browser:": "Hagyja jóvá ezt a kódot a böngészőjében megnyílt oldalon:", + "Archive chat": "Csevegés archiválása", + "Attach files": "Fájlok csatolása", "Auto — asks nothing": "Auto — semmit nem kérdez", "Cancel": "Mégse", "Carry it out": "Végrehajtás", @@ -541,11 +676,17 @@ const LEMBAS_I18N = { "Close": "Bezárás", "Close (LLeMbas stays in the tray)": "Bezárás (a LLeMbas a tálcán marad)", "Command": "Parancs", + "Commands": "Parancsok", "Connect": "Csatlakozás", "Copy": "Másolás", "Cut short here; the whole of it is in the web interface.": "Itt rövidítve; a teljes a webes felületen van.", + "Delete": "Törlés", + "Delete chat": "Csevegés törlése", + "Delete this chat?": "Törli ezt a csevegést?", "Deny": "Elutasítás", + "Drop the files to attach them": "Engedje el a fájlokat a csatoláshoz", "Earlier messages": "Korábbi üzenetek", + "Edit message": "Üzenet szerkesztése", "Edit — asks before commands": "Edit — parancsok előtt kérdez", "Empty folder": "Üres mappa", "Folder": "Mappa", @@ -560,6 +701,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — előbb kérdez", "Maximise": "Teljes méret", "Menu": "Menü", + "Message": "Üzenet", "Minimise": "Kis méret", "Mode": "Mód", "Model": "Modell", @@ -573,9 +715,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Ennek a fióknak nem érhető el modell.", "No project folder yet: the agent can open nothing on this computer.": "Még nincs projektmappa: az ügynök semmit sem nyithat meg ezen a számítógépen.", "Not yet": "Még nem", + "Nothing matches.": "Nincs találat.", "Notifications": "Értesítések", "Only the agent's folders: Settings, Project folders.": "Csak az ügynök mappái: Beállítások, Projektmappák.", "Open in the viewer": "Megnyitás a megjelenítőben", + "Open in the web interface": "Megnyitás a webes felületen", "Open the page again": "Oldal újbóli megnyitása", "Or type an answer": "Vagy írjon választ", "Other devices": "Más eszközök", @@ -584,12 +728,22 @@ const LEMBAS_I18N = { "Preview": "Előnézet", "Project folders": "Projektmappák", "Read again": "Újraolvasás", + "Reasoning effort": "Gondolkodási erőfeszítés", "Recommended": "Ajánlott", + "Regenerate reply": "Válasz újragenerálása", "Remove": "Eltávolítás", "Remove %(dir)s": "%(dir)s eltávolítása", + "Rename": "Átnevezés", + "Rename chat": "Csevegés átnevezése", + "Rename…": "Átnevezés…", "Restore": "Visszaállítás", + "Save": "Mentés", + "Saving starts the reply again; everything after this message is dropped.": "A mentés újraindítja a választ; minden, ami ez után az üzenet után jön, elvész.", + "Search": "Keresés", "Send": "Küldés", + "Sending…": "Küldés…", "Server": "Kiszolgáló", + "Server files": "Fájlok a szerveren", "Settings": "Beállítások", "Shown by the agent": "Az ügynök mutatta", "Sign in": "Bejelentkezés", @@ -607,12 +761,15 @@ const LEMBAS_I18N = { "The agent asks:": "Az ügynök kérdezi:", "The agent has finished.": "Az ügynök befejezte.", "The agent is connected, but may open no folder yet: add one below.": "Az ügynök csatlakozott, de még egy mappát sem nyithat meg: adjon hozzá egyet lent.", + "The agent is connecting to the server…": "Az ügynök csatlakozik a szerverhez…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "Az ügynök még nem kapcsolódik a szerverhez: indítsa el, vagy kapcsolja össze ezt a számítógépet a Beállításokban.", + "The agent is not connected: %(why)s. It keeps trying.": "Az ügynök nincs csatlakozva: %(why)s. Tovább próbálkozik.", "The agent is off.": "Az ügynök ki van kapcsolva.", "The agent is running: agent chats can use this computer.": "Az ügynök fut: az ügynök-csevegések használhatják ezt a számítógépet.", "The agent is starting…": "Az ügynök indul…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "Az ügynök lehetővé teszi, hogy a kiszolgálón futó ügynök-csevegések ennek a számítógépnek a projektjeiben dolgozzanak. Ez a beépített LLeMbas CLI, amely a háttérben fut, amíg az alkalmazás fut.", "The agent stopped: %(why)s. Trying again shortly.": "Az ügynök leállt: %(why)s. Hamarosan újra próbálja.", + "The agent will not connect: %(why)s. Link this computer again.": "Az ügynök nem fog csatlakozni: %(why)s. Kapcsolja össze újra ezt a számítógépet.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "Az alkalmazás egyszer jelentkezik be a szerverére, a böngészőjében, mint bármely eszköz. Ezután olvashatja és írhatja a csevegéseit, mint a webes felület, és ugyanez a jóváhagyás összekapcsolja ezt a számítógépet az ügynök számára.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "Az alkalmazás egyszer jelentkezik be a szerverére, a böngészőjében, mint bármely eszköz. Ezután olvashatja és írhatja a csevegéseit, mint a webes felület.", "The connection to the reply was lost: %(why)s": "A kapcsolat a válasszal megszakadt: %(why)s", @@ -622,27 +779,36 @@ const LEMBAS_I18N = { "This computer": "Ez a számítógép", "This computer is not linked to the server yet.": "Ez a számítógép még nincs összekapcsolva a kiszolgálóval.", "This file is too big to show here (%(size)s).": "Ez a fájl túl nagy a megjelenítéshez (%(size)s).", + "This file is too large to send.": "Ez a fájl túl nagy a küldéshez.", + "This is a folder; attach the files in it.": "Ez egy mappa; a benne lévő fájlokat csatolja.", "This is not a text file; it cannot be shown here.": "Ez nem szövegfájl; itt nem jeleníthető meg.", "Thought": "Gondolkodott", "Try again": "Újrapróbálás", + "Type a command or a chat's title": "Írjon be egy parancsot vagy egy csevegés címét", + "Wait until the files are sent.": "Várjon, amíg a fájlok elküldődnek.", "Waiting for the browser…": "Várakozás a böngészőre…", "Waiting for the reply before it": "Az előző válaszra vár", "Waiting for your answer": "Az Ön válaszára vár", "Writing…": "Írás…", "Your answer": "Az Ön válasza", + "agent connecting…": "ügynök csatlakozik…", "agent off": "ügynök kikapcsolva", + "agent refused": "ügynök elutasítva", "agent running": "ügynök fut", "agent running (service)": "ügynök fut (szolgáltatás)", "agent starting…": "ügynök indul…", "agent stopped": "ügynök leállt", + "agent waiting": "ügynök vár", "agent: no folders": "ügynök: nincs mappa", "not linked": "nincs összekapcsolva", "update": "frissítés", - "update %(version)s": "frissítés: %(version)s" + "update %(version)s": "frissítés: %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s” törlődik a szerverről, az üzeneteivel és fájljaival együtt. Ez nem vonható vissza." }, "it": { "%(n)s tokens": "token: %(n)s", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Un servizio LLeMbas CLI è già in esecuzione qui (pid %(pid)s), ma non è collegato: %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Un servizio LLeMbas CLI è già in esecuzione qui (pid %(pid)s); è l'agente di questo computer.", "A chat asking for more is lowered to this.": "Una chat che chiede di più viene limitata a questo.", "A chat asking for more than the folder allows is lowered to it.": "Una chat che chiede più di quanto la cartella consente viene ridotta a quello.", @@ -652,13 +818,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "Indirizzo del tuo server LLeMbas", "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 stopped: %(why)s": "L'agente si è fermato: %(why)s", + "Agent: %(why)s": "Agente: %(why)s", "Allow": "Consenti", "Always allow": "Consenti sempre", "An agent chat works in a folder on this computer, through the agent built into this app.": "Una chat con agente lavora in una cartella di questo computer, tramite l'agente integrato in questa app.", "Another device": "Un altro dispositivo", "Answer": "Rispondi", "Approve this code in the page that opened in your browser:": "Approvi questo codice nella pagina che si è aperta nel suo browser:", + "Archive chat": "Archivia chat", + "Attach files": "Allegare file", "Auto — asks nothing": "Auto — non chiede nulla", "Cancel": "Annulla", "Carry it out": "Eseguilo", @@ -667,11 +835,17 @@ const LEMBAS_I18N = { "Close": "Chiudi", "Close (LLeMbas stays in the tray)": "Chiudi (LLeMbas resta nell'area di notifica)", "Command": "Comando", + "Commands": "Comandi", "Connect": "Connetti", "Copy": "Copia", "Cut short here; the whole of it is in the web interface.": "Troncato qui; il resto è nell'interfaccia web.", + "Delete": "Elimina", + "Delete chat": "Elimina chat", + "Delete this chat?": "Eliminare questa chat?", "Deny": "Nega", + "Drop the files to attach them": "Rilasci i file per allegarli", "Earlier messages": "Messaggi precedenti", + "Edit message": "Modifica messaggio", "Edit — asks before commands": "Edit — chiede prima dei comandi", "Empty folder": "Cartella vuota", "Folder": "Cartella", @@ -686,6 +860,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — chiede prima", "Maximise": "Ingrandisci", "Menu": "Menu", + "Message": "Messaggio", "Minimise": "Riduci a icona", "Mode": "Modalità", "Model": "Modello", @@ -699,9 +874,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Nessun modello è disponibile per questo account.", "No project folder yet: the agent can open nothing on this computer.": "Nessuna cartella di progetto ancora: l'agente non può aprire nulla su questo computer.", "Not yet": "Non ancora", + "Nothing matches.": "Nessun risultato.", "Notifications": "Notifiche", "Only the agent's folders: Settings, Project folders.": "Solo le cartelle dell'agente: Impostazioni, Cartelle di progetto.", "Open in the viewer": "Apri nel visualizzatore", + "Open in the web interface": "Apri nell'interfaccia web", "Open the page again": "Riapri la pagina", "Or type an answer": "Oppure scriva una risposta", "Other devices": "Altri dispositivi", @@ -710,12 +887,22 @@ const LEMBAS_I18N = { "Preview": "Anteprima", "Project folders": "Cartelle dei progetti", "Read again": "Rileggi", + "Reasoning effort": "Sforzo di ragionamento", "Recommended": "Consigliato", + "Regenerate reply": "Rigenera risposta", "Remove": "Rimuovi", "Remove %(dir)s": "Rimuovi %(dir)s", + "Rename": "Rinomina", + "Rename chat": "Rinomina chat", + "Rename…": "Rinomina…", "Restore": "Ripristina", + "Save": "Salva", + "Saving starts the reply again; everything after this message is dropped.": "Salvando, la risposta riparte; tutto ciò che segue questo messaggio viene scartato.", + "Search": "Cerca", "Send": "Invia", + "Sending…": "Invio…", "Server": "Server", + "Server files": "File sul server", "Settings": "Impostazioni", "Shown by the agent": "Mostrato dall'agente", "Sign in": "Accedi", @@ -733,12 +920,15 @@ const LEMBAS_I18N = { "The agent asks:": "L'agente chiede:", "The agent has finished.": "L'agente ha finito.", "The agent is connected, but may open no folder yet: add one below.": "L'agente è connesso, ma non può ancora aprire nessuna cartella: aggiungine una qui sotto.", + "The agent is connecting to the server…": "L'agente si sta collegando al server…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "L'agente non è ancora connesso al server: lo avvii o colleghi questo computer nelle Impostazioni.", + "The agent is not connected: %(why)s. It keeps trying.": "L'agente non è collegato: %(why)s. Continua a riprovare.", "The agent is off.": "L'agente è spento.", "The agent is running: agent chats can use this computer.": "L'agente è in esecuzione: le chat agente possono usare questo computer.", "The agent is starting…": "L'agente si sta avviando…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "L'agente permette alle chat agente del tuo server di lavorare nei progetti di questo computer. È il LLeMbas CLI, integrato, in esecuzione in background finché l'app è aperta.", "The agent stopped: %(why)s. Trying again shortly.": "L'agente si è fermato: %(why)s. Tra poco riprova.", + "The agent will not connect: %(why)s. Link this computer again.": "L'agente non si collegherà: %(why)s. Colleghi di nuovo questo computer.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "L'app accede al suo server una volta, nel suo browser, come ogni dispositivo. Poi può leggere e scrivere le sue chat, come l'interfaccia web, e la stessa approvazione collega questo computer per l'agente.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "L'app accede al suo server una volta, nel suo browser, come ogni dispositivo. Poi può leggere e scrivere le sue chat, come l'interfaccia web.", "The connection to the reply was lost: %(why)s": "La connessione alla risposta si è interrotta: %(why)s", @@ -748,27 +938,36 @@ const LEMBAS_I18N = { "This computer": "Questo computer", "This computer is not linked to the server yet.": "Questo computer non è ancora collegato al server.", "This file is too big to show here (%(size)s).": "Questo file è troppo grande per essere mostrato (%(size)s).", + "This file is too large to send.": "Questo file è troppo grande per essere inviato.", + "This is a folder; attach the files in it.": "Questa è una cartella; alleghi i file che contiene.", "This is not a text file; it cannot be shown here.": "Questo non è un file di testo; non può essere mostrato qui.", "Thought": "Ha ragionato", "Try again": "Riprova", + "Type a command or a chat's title": "Digiti un comando o il titolo di una chat", + "Wait until the files are sent.": "Attenda che i file siano inviati.", "Waiting for the browser…": "In attesa del browser…", "Waiting for the reply before it": "In attesa della risposta precedente", "Waiting for your answer": "In attesa della sua risposta", "Writing…": "Sta scrivendo…", "Your answer": "La sua risposta", + "agent connecting…": "agente in collegamento…", "agent off": "agente spento", + "agent refused": "agente rifiutato", "agent running": "agente attivo", "agent running (service)": "agente attivo (servizio)", "agent starting…": "agente in avvio…", "agent stopped": "agente fermato", + "agent waiting": "agente in attesa", "agent: no folders": "agente: nessuna cartella", "not linked": "non collegato", "update": "aggiorna", - "update %(version)s": "aggiorna a %(version)s" + "update %(version)s": "aggiorna a %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "«%(title)s» viene eliminata dal server, con i suoi messaggi e file. Non si può annullare." }, "pl": { "%(n)s tokens": "Tokeny: %(n)s", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Usługa LLeMbas CLI już tu działa (pid %(pid)s), ale nie jest połączona: %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Usługa LLeMbas CLI już tu działa (pid %(pid)s); jest agentem tego komputera.", "A chat asking for more is lowered to this.": "Czat, który prosi o więcej, zostaje ograniczony do tego.", "A chat asking for more than the folder allows is lowered to it.": "Czat, który chce więcej, niż folder pozwala, zostaje do tego obniżony.", @@ -778,13 +977,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "Adres Twojego serwera LLeMbas", "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 stopped: %(why)s": "Agent się zatrzymał: %(why)s", + "Agent: %(why)s": "Agent: %(why)s", "Allow": "Pozwól", "Always allow": "Zawsze zezwalaj", "An agent chat works in a folder on this computer, through the agent built into this app.": "Czat z agentem pracuje w folderze na tym komputerze, przez agenta wbudowanego w tę aplikację.", "Another device": "Inne urządzenie", "Answer": "Odpowiedź", "Approve this code in the page that opened in your browser:": "Zatwierdź ten kod na stronie, która otworzyła się w przeglądarce:", + "Archive chat": "Archiwizuj czat", + "Attach files": "Załączaj pliki", "Auto — asks nothing": "Auto — o nic nie pyta", "Cancel": "Anuluj", "Carry it out": "Wykonaj", @@ -793,11 +994,17 @@ const LEMBAS_I18N = { "Close": "Zamknij", "Close (LLeMbas stays in the tray)": "Zamknij (LLeMbas zostaje w zasobniku systemowym)", "Command": "Polecenie", + "Commands": "Polecenia", "Connect": "Połącz", "Copy": "Kopiuj", "Cut short here; the whole of it is in the web interface.": "Tu skrócone; całość jest w interfejsie WWW.", + "Delete": "Usuń", + "Delete chat": "Usuń czat", + "Delete this chat?": "Usunąć ten czat?", "Deny": "Odrzuć", + "Drop the files to attach them": "Upuść pliki, aby je dołączyć", "Earlier messages": "Wcześniejsze wiadomości", + "Edit message": "Edytuj wiadomość", "Edit — asks before commands": "Edit — pyta przed poleceniami", "Empty folder": "Pusty folder", "Folder": "Folder", @@ -812,6 +1019,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — najpierw pyta", "Maximise": "Maksymalizuj", "Menu": "Menu", + "Message": "Wiadomość", "Minimise": "Minimalizuj", "Mode": "Tryb", "Model": "Model", @@ -825,9 +1033,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Dla tego konta nie jest dostępny żaden model.", "No project folder yet: the agent can open nothing on this computer.": "Jeszcze nie ma folderu projektu: agent nie może niczego otworzyć na tym komputerze.", "Not yet": "Jeszcze nie", + "Nothing matches.": "Nic nie pasuje.", "Notifications": "Powiadomienia", "Only the agent's folders: Settings, Project folders.": "Tylko foldery agenta: Ustawienia, Foldery projektów.", "Open in the viewer": "Otwórz w podglądzie", + "Open in the web interface": "Otwórz w interfejsie WWW", "Open the page again": "Otwórz stronę ponownie", "Or type an answer": "Albo wpisz odpowiedź", "Other devices": "Inne urządzenia", @@ -836,12 +1046,22 @@ const LEMBAS_I18N = { "Preview": "Podgląd", "Project folders": "Foldery projektów", "Read again": "Wczytaj ponownie", + "Reasoning effort": "Poziom rozumowania", "Recommended": "Zalecane", + "Regenerate reply": "Wygeneruj odpowiedź ponownie", "Remove": "Usuń", "Remove %(dir)s": "Usuń %(dir)s", + "Rename": "Zmień nazwę", + "Rename chat": "Zmień nazwę czatu", + "Rename…": "Zmień nazwę…", "Restore": "Przywróć", + "Save": "Zapisz", + "Saving starts the reply again; everything after this message is dropped.": "Zapisanie uruchamia odpowiedź od nowa; wszystko po tej wiadomości zostanie odrzucone.", + "Search": "Szukaj", "Send": "Wyślij", + "Sending…": "Wysyłanie…", "Server": "Serwer", + "Server files": "Pliki na serwerze", "Settings": "Ustawienia", "Shown by the agent": "Pokazane przez agenta", "Sign in": "Zaloguj się", @@ -859,12 +1079,15 @@ const LEMBAS_I18N = { "The agent asks:": "Agent pyta:", "The agent has finished.": "Agent skończył.", "The agent is connected, but may open no folder yet: add one below.": "Agent jest połączony, ale nie może jeszcze otworzyć żadnego folderu: dodaj go poniżej.", + "The agent is connecting to the server…": "Agent łączy się z serwerem…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "Agent nie jest jeszcze połączony z serwerem: uruchom go lub połącz ten komputer w Ustawieniach.", + "The agent is not connected: %(why)s. It keeps trying.": "Agent nie jest połączony: %(why)s. Próbuje dalej.", "The agent is off.": "Agent jest wyłączony.", "The agent is running: agent chats can use this computer.": "Agent działa: czaty agenta mogą używać tego komputera.", "The agent is starting…": "Agent się uruchamia…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "Agent pozwala czatom agenta z Twojego serwera pracować w projektach na tym komputerze. To wbudowane LLeMbas CLI, działające w tle, dopóki działa aplikacja.", "The agent stopped: %(why)s. Trying again shortly.": "Agent się zatrzymał: %(why)s. Za chwilę spróbuje ponownie.", + "The agent will not connect: %(why)s. Link this computer again.": "Agent się nie połączy: %(why)s. Połącz ten komputer ponownie.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "Aplikacja loguje się do Twojego serwera raz, w przeglądarce, jak każde urządzenie. Potem może czytać i pisać Twoje czaty, jak interfejs WWW, a to samo zatwierdzenie łączy ten komputer dla agenta.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "Aplikacja loguje się do Twojego serwera raz, w przeglądarce, jak każde urządzenie. Potem może czytać i pisać Twoje czaty, jak interfejs WWW.", "The connection to the reply was lost: %(why)s": "Połączenie z odpowiedzią zostało zerwane: %(why)s", @@ -874,27 +1097,36 @@ const LEMBAS_I18N = { "This computer": "Ten komputer", "This computer is not linked to the server yet.": "Ten komputer nie jest jeszcze połączony z serwerem.", "This file is too big to show here (%(size)s).": "Ten plik jest za duży, by go tu pokazać (%(size)s).", + "This file is too large to send.": "Ten plik jest za duży, aby go wysłać.", + "This is a folder; attach the files in it.": "To jest folder; dołącz pliki, które zawiera.", "This is not a text file; it cannot be shown here.": "To nie jest plik tekstowy; nie można go tu pokazać.", "Thought": "Rozumowanie", "Try again": "Spróbuj ponownie", + "Type a command or a chat's title": "Wpisz polecenie lub tytuł czatu", + "Wait until the files are sent.": "Poczekaj, aż pliki zostaną wysłane.", "Waiting for the browser…": "Czekam na przeglądarkę…", "Waiting for the reply before it": "Czeka na odpowiedź przed nią", "Waiting for your answer": "Czeka na Twoją odpowiedź", "Writing…": "Pisze…", "Your answer": "Twoja odpowiedź", + "agent connecting…": "agent łączy się…", "agent off": "agent wyłączony", + "agent refused": "agent odrzucony", "agent running": "agent działa", "agent running (service)": "agent działa (usługa)", "agent starting…": "agent się uruchamia…", "agent stopped": "agent zatrzymany", + "agent waiting": "agent czeka", "agent: no folders": "agent: brak folderów", "not linked": "niepołączony", "update": "aktualizacja", - "update %(version)s": "aktualizacja %(version)s" + "update %(version)s": "aktualizacja %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s” zostanie usunięty z serwera wraz z wiadomościami i plikami. Tego nie można cofnąć." }, "sk": { "%(n)s tokens": "tokeny: %(n)s", "%(name)s, LLeMbas %(version)s.": "%(name)s, LLeMbas %(version)s.", + "A LLeMbas CLI service already runs here (pid %(pid)s), but is not connected: %(why)s": "Služba LLeMbas CLI tu už beží (pid %(pid)s), ale nie je pripojená: %(why)s", "A LLeMbas CLI service already runs here (pid %(pid)s); it is this computer's agent.": "Služba LLeMbas CLI tu už beží (pid %(pid)s); je agentom tohto počítača.", "A chat asking for more is lowered to this.": "Konverzácia, ktorá chce viac, je znížená na toto.", "A chat asking for more than the folder allows is lowered to it.": "Konverzácia, ktorá chce viac, než priečinok dovoľuje, je znížená na to.", @@ -904,13 +1136,15 @@ const LEMBAS_I18N = { "Address of your LLeMbas server": "Adresa vášho servera LLeMbas", "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 stopped: %(why)s": "Agent sa zastavil: %(why)s", + "Agent: %(why)s": "Agent: %(why)s", "Allow": "Povoliť", "Always allow": "Vždy povoliť", "An agent chat works in a folder on this computer, through the agent built into this app.": "Agentská konverzácia pracuje v priečinku na tomto počítači cez agenta vstavaného v tejto aplikácii.", "Another device": "Iné zariadenie", "Answer": "Odpovedať", "Approve this code in the page that opened in your browser:": "Potvrďte tento kód na stránke, ktorá sa otvorila vo vašom prehliadači:", + "Archive chat": "Archivovať konverzáciu", + "Attach files": "Prikladať súbory", "Auto — asks nothing": "Auto — na nič sa nepýta", "Cancel": "Zrušiť", "Carry it out": "Vykonať to", @@ -919,11 +1153,17 @@ const LEMBAS_I18N = { "Close": "Zavrieť", "Close (LLeMbas stays in the tray)": "Zavrieť (LLeMbas zostane v oznamovacej oblasti)", "Command": "Príkaz", + "Commands": "Príkazy", "Connect": "Pripojiť", "Copy": "Kopírovať", "Cut short here; the whole of it is in the web interface.": "Tu skrátené; celé je to vo webovom rozhraní.", + "Delete": "Zmazať", + "Delete chat": "Zmazať konverzáciu", + "Delete this chat?": "Zmazať túto konverzáciu?", "Deny": "Odmietnuť", + "Drop the files to attach them": "Pustite súbory a priložia sa", "Earlier messages": "Staršie správy", + "Edit message": "Upraviť správu", "Edit — asks before commands": "Edit — pýta sa pred príkazmi", "Empty folder": "Prázdny priečinok", "Folder": "Priečinok", @@ -938,6 +1178,7 @@ const LEMBAS_I18N = { "Manual — asks first": "Manual — najprv sa opýta", "Maximise": "Maximalizovať", "Menu": "Ponuka", + "Message": "Správa", "Minimise": "Minimalizovať", "Mode": "Režim", "Model": "Model", @@ -951,9 +1192,11 @@ const LEMBAS_I18N = { "No model is available to this account.": "Tomuto účtu nie je dostupný žiadny model.", "No project folder yet: the agent can open nothing on this computer.": "Zatiaľ žiadny priečinok projektu: agent na tomto počítači nemôže nič otvoriť.", "Not yet": "Ešte nie", + "Nothing matches.": "Nič nezodpovedá.", "Notifications": "Upozornenia", "Only the agent's folders: Settings, Project folders.": "Len priečinky agenta: Nastavenia, Priečinky projektov.", "Open in the viewer": "Otvoriť v prehliadači súborov", + "Open in the web interface": "Otvoriť vo webovom rozhraní", "Open the page again": "Otvoriť stránku znova", "Or type an answer": "Alebo napíšte odpoveď", "Other devices": "Iné zariadenia", @@ -962,12 +1205,22 @@ const LEMBAS_I18N = { "Preview": "Náhľad", "Project folders": "Priečinky projektov", "Read again": "Načítať znova", + "Reasoning effort": "Náročnosť uvažovania", "Recommended": "Odporúčané", + "Regenerate reply": "Vygenerovať odpoveď znova", "Remove": "Odstrániť", "Remove %(dir)s": "Odstrániť %(dir)s", + "Rename": "Premenovať", + "Rename chat": "Premenovať konverzáciu", + "Rename…": "Premenovať…", "Restore": "Obnoviť", + "Save": "Uložiť", + "Saving starts the reply again; everything after this message is dropped.": "Uložením sa odpoveď spustí znova; všetko po tejto správe sa zahodí.", + "Search": "Hľadať", "Send": "Poslať", + "Sending…": "Odosiela sa…", "Server": "Server", + "Server files": "Súbory na serveri", "Settings": "Nastavenia", "Shown by the agent": "Ukázal agent", "Sign in": "Prihlásiť sa", @@ -985,12 +1238,15 @@ const LEMBAS_I18N = { "The agent asks:": "Agent sa pýta:", "The agent has finished.": "Agent dokončil prácu.", "The agent is connected, but may open no folder yet: add one below.": "Agent je pripojený, ale zatiaľ nesmie otvoriť žiadny priečinok: pridajte ho nižšie.", + "The agent is connecting to the server…": "Agent sa pripája k serveru…", "The agent is not connected to the server yet: start it, or link this computer, under Settings.": "Agent ešte nie je pripojený k serveru: spustite ho alebo prepojte tento počítač v Nastaveniach.", + "The agent is not connected: %(why)s. It keeps trying.": "Agent nie je pripojený: %(why)s. Skúša to ďalej.", "The agent is off.": "Agent je vypnutý.", "The agent is running: agent chats can use this computer.": "Agent beží: agentské konverzácie môžu používať tento počítač.", "The agent is starting…": "Agent sa spúšťa…", "The agent lets agent chats on your server work in this computer's projects. It is the LLeMbas CLI, built in, running in the background while the app runs.": "Agent umožňuje agentským konverzáciám na vašom serveri pracovať v projektoch na tomto počítači. Je to vstavané LLeMbas CLI, ktoré beží na pozadí, kým beží aplikácia.", "The agent stopped: %(why)s. Trying again shortly.": "Agent sa zastavil: %(why)s. O chvíľu to skúsi znova.", + "The agent will not connect: %(why)s. Link this computer again.": "Agent sa nepripojí: %(why)s. Prepojte tento počítač znova.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does, and the same approval links this computer for the agent.": "Aplikácia sa na váš server prihlási raz, vo vašom prehliadači, ako každé zariadenie. Potom môže čítať a písať vaše konverzácie, ako webové rozhranie, a to isté schválenie prepojí tento počítač pre agenta.", "The app signs in to your server once, in your browser, as any device does. It may then read and write your chats, as the web interface does.": "Aplikácia sa na váš server prihlási raz, vo vašom prehliadači, ako každé zariadenie. Potom môže čítať a písať vaše konverzácie, ako webové rozhranie.", "The connection to the reply was lost: %(why)s": "Spojenie s odpoveďou sa prerušilo: %(why)s", @@ -1000,23 +1256,31 @@ const LEMBAS_I18N = { "This computer": "Tento počítač", "This computer is not linked to the server yet.": "Tento počítač ešte nie je prepojený so serverom.", "This file is too big to show here (%(size)s).": "Tento súbor je príliš veľký na zobrazenie (%(size)s).", + "This file is too large to send.": "Tento súbor je príliš veľký na odoslanie.", + "This is a folder; attach the files in it.": "Toto je priečinok; priložte súbory v ňom.", "This is not a text file; it cannot be shown here.": "Toto nie je textový súbor; nedá sa tu zobraziť.", "Thought": "Premýšľal", "Try again": "Skúsiť znova", + "Type a command or a chat's title": "Napíšte príkaz alebo názov konverzácie", + "Wait until the files are sent.": "Počkajte, kým sa súbory odošlú.", "Waiting for the browser…": "Čaká sa na prehliadač…", "Waiting for the reply before it": "Čaká na odpoveď pred ňou", "Waiting for your answer": "Čaká na vašu odpoveď", "Writing…": "Píše sa…", "Your answer": "Vaša odpoveď", + "agent connecting…": "agent sa pripája…", "agent off": "agent vypnutý", + "agent refused": "agent odmietnutý", "agent running": "agent beží", "agent running (service)": "agent beží (služba)", "agent starting…": "agent sa spúšťa…", "agent stopped": "agent zastavený", + "agent waiting": "agent čaká", "agent: no folders": "agent: žiadne priečinky", "not linked": "neprepojené", "update": "aktualizácia", - "update %(version)s": "aktualizácia %(version)s" + "update %(version)s": "aktualizácia %(version)s", + "“%(title)s” is deleted on the server, with its messages and files. This cannot be undone.": "„%(title)s“ sa zmaže na serveri aj so svojimi správami a súbormi. Nedá sa to vrátiť." } } diff --git a/ui/index.html b/ui/index.html index 803c624..b375e67 100644 --- a/ui/index.html +++ b/ui/index.html @@ -87,8 +87,11 @@
-