diff --git a/CHANGELOG.md b/CHANGELOG.md index 256cc68..28c3212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,9 @@ All notable changes to LLeMbas Desktop. The format follows with Open and Run again (and Pause, when a task has failed three times in a row), labelled in the account's language. Open, or a click on the notification, opens the task; Run again and Pause do it without opening the window. A chat's notification, clicked, opens that chat. +- **The release your server recommends.** Settings › About names the Desktop release that goes + with the server's version when it is newer than this one (a beta only to a beta install), with + a link to its release page. ### Fixed diff --git a/scripts/fetch-fixtures.sh b/scripts/fetch-fixtures.sh index 077fe1d..b133734 100755 --- a/scripts/fetch-fixtures.sh +++ b/scripts/fetch-fixtures.sh @@ -16,7 +16,7 @@ OUT="tests/ui/fixtures" FILES="chat_agent.json event_ask.json event_status.json event_status_idle.json event_status_person.json event_status_stopped.json event_tree.json models_agent.json subagent_findings.json subagent_transcript.json tree.json me.json folders.json folder.json search.json memories.json memory.json scope.json push_schedule.json push_schedule_failed.json -event_schedules.json schedule.json schedule_run.json chat.json chats_listed.json files_listing.json" +apps.json apps_stable.json event_schedules.json schedule.json schedule_run.json chat.json chats_listed.json files_listing.json" [ $# -eq 1 ] || { echo "usage: scripts/fetch-fixtures.sh " >&2; exit 2; } commit="$(git -C "$SERVER" rev-parse --verify --quiet "$1^{commit}")" \ diff --git a/scripts/gen-i18n.py b/scripts/gen-i18n.py index 9c56bfe..ec4a7ec 100755 --- a/scripts/gen-i18n.py +++ b/scripts/gen-i18n.py @@ -520,6 +520,10 @@ KEYS = ( "Draw", "Drawing… a picture can take a minute or two.", "The server's answer had no picture in it.", + # settings.js: the release the server recommends + "Your server recommends LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.", + "Open the release page", ) # The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the @@ -790,6 +794,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Nakreslit", "Drawing… a picture can take a minute or two.": "Kreslím… obrázek může trvat minutu nebo dvě.", "The server's answer had no picture in it.": "V odpovědi serveru nebyl žádný obrázek.", + "Your server recommends LLeMbas Desktop %(version)s": "Váš server doporučuje LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.": "Vydání, které patří k verzi serveru. Toto je %(current)s.", + "Open the release page": "Otevřít stránku vydání", }, "de": { "Sign in again: the server no longer accepts this app's sign-in.": "Melden Sie sich erneut an: Der Server nimmt die Anmeldung dieser App nicht mehr an.", @@ -1055,6 +1062,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Zeichnen", "Drawing… a picture can take a minute or two.": "Wird gezeichnet… ein Bild kann ein bis zwei Minuten dauern.", "The server's answer had no picture in it.": "Die Antwort des Servers enthielt kein Bild.", + "Your server recommends LLeMbas Desktop %(version)s": "Ihr Server empfiehlt LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.": "Die Version, die zur Version des Servers passt. Dies ist %(current)s.", + "Open the release page": "Release-Seite öffnen", }, "es": { "Sign in again: the server no longer accepts this app's sign-in.": "Vuelva a iniciar sesión: el servidor ya no acepta el inicio de sesión de esta aplicación.", @@ -1320,6 +1330,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Dibujar", "Drawing… a picture can take a minute or two.": "Dibujando… una imagen puede tardar uno o dos minutos.", "The server's answer had no picture in it.": "La respuesta del servidor no traía ninguna imagen.", + "Your server recommends LLeMbas Desktop %(version)s": "Su servidor recomienda LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.": "La versión que corresponde a la del servidor. Esta es %(current)s.", + "Open the release page": "Abrir la página de la versión", }, "fr": { "Sign in again: the server no longer accepts this app's sign-in.": "Reconnectez-vous : le serveur n’accepte plus la connexion de cette application.", @@ -1585,6 +1598,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Dessiner", "Drawing… a picture can take a minute or two.": "Dessin en cours… une image peut prendre une minute ou deux.", "The server's answer had no picture in it.": "La réponse du serveur ne contenait aucune image.", + "Your server recommends LLeMbas Desktop %(version)s": "Votre serveur recommande LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.": "La version qui va avec celle du serveur. Celle-ci est la %(current)s.", + "Open the release page": "Ouvrir la page de la version", }, "hu": { "Sign in again: the server no longer accepts this app's sign-in.": "Jelentkezzen be újra: a szerver már nem fogadja el ennek az alkalmazásnak a bejelentkezését.", @@ -1850,6 +1866,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Rajzolás", "Drawing… a picture can take a minute or two.": "Rajzolás… egy kép egy-két percig is eltarthat.", "The server's answer had no picture in it.": "A szerver válaszában nem volt kép.", + "Your server recommends LLeMbas Desktop %(version)s": "A szervere a LLeMbas Desktop %(version)s verziót ajánlja", + "The release that goes with the server's version. This is %(current)s.": "A szerver verziójához tartozó kiadás. Ez a %(current)s.", + "Open the release page": "Kiadás oldalának megnyitása", }, "it": { "Sign in again: the server no longer accepts this app's sign-in.": "Accedi di nuovo: il server non accetta più l’accesso di questa app.", @@ -2115,6 +2134,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Disegna", "Drawing… a picture can take a minute or two.": "Disegno in corso… un’immagine può richiedere un minuto o due.", "The server's answer had no picture in it.": "La risposta del server non conteneva immagini.", + "Your server recommends LLeMbas Desktop %(version)s": "Il tuo server consiglia LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.": "La versione che va con quella del server. Questa è la %(current)s.", + "Open the release page": "Apri la pagina della versione", }, "pl": { "Sign in again: the server no longer accepts this app's sign-in.": "Zaloguj się ponownie: serwer nie przyjmuje już logowania tej aplikacji.", @@ -2380,6 +2402,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Narysuj", "Drawing… a picture can take a minute or two.": "Rysowanie… obraz może zająć minutę lub dwie.", "The server's answer had no picture in it.": "W odpowiedzi serwera nie było obrazu.", + "Your server recommends LLeMbas Desktop %(version)s": "Twój serwer zaleca LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.": "Wydanie pasujące do wersji serwera. To jest %(current)s.", + "Open the release page": "Otwórz stronę wydania", }, "sk": { "Sign in again: the server no longer accepts this app's sign-in.": "Prihláste sa znova: server už neprijíma prihlásenie tejto aplikácie.", @@ -2645,6 +2670,9 @@ OWN: dict[str, dict[str, str]] = { "Draw": "Nakresliť", "Drawing… a picture can take a minute or two.": "Kreslím… obrázok môže trvať minútu alebo dve.", "The server's answer had no picture in it.": "V odpovedi servera nebol žiadny obrázok.", + "Your server recommends LLeMbas Desktop %(version)s": "Váš server odporúča LLeMbas Desktop %(version)s", + "The release that goes with the server's version. This is %(current)s.": "Vydanie, ktoré patrí k verzii servera. Toto je %(current)s.", + "Open the release page": "Otvoriť stránku vydania", }, } diff --git a/tests/ui/fixtures/apps.json b/tests/ui/fixtures/apps.json new file mode 100644 index 0000000..da8c92c --- /dev/null +++ b/tests/ui/fixtures/apps.json @@ -0,0 +1,20 @@ +{ + "server": { + "version": "2.1.0-beta.3", + "channel": "beta" + }, + "apps": { + "android": { + "version": "1.1.0-beta.2", + "tag": "v1.1.0-beta.2", + "channel": "beta", + "page": "https://github.com/LLeMbas/LLeMbas-Android/releases/tag/v1.1.0-beta.2" + }, + "desktop": { + "version": "1.0.1", + "tag": "v1.0.1", + "channel": "stable", + "page": "https://github.com/LLeMbas/LLeMbas-Desktop/releases/tag/v1.0.1" + } + } +} diff --git a/tests/ui/fixtures/apps_stable.json b/tests/ui/fixtures/apps_stable.json new file mode 100644 index 0000000..45cb069 --- /dev/null +++ b/tests/ui/fixtures/apps_stable.json @@ -0,0 +1,15 @@ +{ + "server": { + "version": "2.0.0", + "channel": "stable" + }, + "apps": { + "android": null, + "desktop": { + "version": "1.0.1", + "tag": "v1.0.1", + "channel": "stable", + "page": "https://github.com/LLeMbas/LLeMbas-Desktop/releases/tag/v1.0.1" + } + } +} diff --git a/tests/ui/run.mjs b/tests/ui/run.mjs index 88128a1..a36468a 100644 --- a/tests/ui/run.mjs +++ b/tests/ui/run.mjs @@ -1550,6 +1550,42 @@ test("J8: a scheduled run's notification opens its task, and a chat's opens the assert.deepEqual(p.errors, []) }) +// K8: the release the server recommends (GET /apps), in Settings › About. + +async function aboutWith(version, apps) { + const p = await page({ status: status({ version }), routes: baseRoutes({ "GET /apps": apps }), streams: {} }) + await p.click("#settings-btn") + await p.waitForFunction(() => window.__mock.api("GET", "/apps").length === 1) + await p.waitForTimeout(100) + return p +} + +test("K8: a newer release the server recommends is offered in About, with its release page", async () => { + const p = await aboutWith("1.0.0", fixture("apps_stable")) + await p.waitForSelector("#set-about >> text=Your server recommends LLeMbas Desktop 1.0.1") + await p.click("#set-about .btn >> text=Open the release page") + const opened = await p.evaluate(() => window.__mock.calls.filter((c) => c.cmd === "open_external").map((c) => c.args.url)) + assert.deepEqual(opened, ["https://github.com/LLeMbas/LLeMbas-Desktop/releases/tag/v1.0.1"]) + await shot(p, "k8-about-1280") + assert.deepEqual(p.errors, []) +}) + +test("K8: nothing is offered that is not newer, a beta to a stable install, or a page elsewhere", async () => { + const beta = { apps: { desktop: { version: "1.1.0-beta.2", tag: "v1.1.0-beta.2", channel: "beta", page: "https://github.com/LLeMbas/LLeMbas-Desktop/releases/tag/v1.1.0-beta.2" } } } + for (const [mine, apps, shown] of [ + ["1.0.1", fixture("apps_stable"), false], + ["1.0.0", beta, false], + ["1.0.0-beta.6", beta, true], + ["1.0.0", { apps: { desktop: { version: "1.0.2", page: "https://elsewhere.example/releases/tag/v1.0.2" } } }, false], + ["1.0.0", { __error: { status: 404, detail: "Not Found" } }, false], + ]) { + const p = await aboutWith(mine, apps) + assert.equal(await p.locator("#set-about >> text=Your server recommends").count(), shown ? 1 : 0, `${mine} ← ${JSON.stringify(apps).slice(0, 60)}`) + assert.deepEqual(p.errors, []) + await p.context().close() + } +}) + // --- Run ---------------------------------------------------------------------------------------- const only = process.argv[2] || "" diff --git a/ui/i18n.js b/ui/i18n.js index 0916595..ef8c2ca 100644 --- a/ui/i18n.js +++ b/ui/i18n.js @@ -236,6 +236,7 @@ const LEMBAS_I18N = { "Open the activity panel": "Otevřít panel aktivity", "Open the chat": "Otevřít konverzaci", "Open the page again": "Otevřít stránku znovu", + "Open the release page": "Otevřít stránku vydání", "Or type an answer": "Nebo napište odpověď", "Outputs": "Výstupy", "Page": "Stránka", @@ -364,6 +365,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Čísla počítají i vlastního agenta konverzace: 1 je agent sám, bez podagentů; Max 4 je agent a až 3 podagenti. Podagent běží na modelu konverzace, na jiném jen tehdy, když ho v konverzaci pojmenujete.", "The previous command": "Předchozí příkaz", "The previous command (Ctrl+Shift+↑)": "Předchozí příkaz (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "Vydání, které patří k verzi serveru. Toto je %(current)s.", "The reply is ready.": "Odpověď je hotová.", "The result is no longer there.": "Výsledek už tu není.", "The server refused this computer's link: %(why)s": "Server odmítl propojení tohoto počítače: %(why)s", @@ -424,6 +426,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Požádali jste: „%(text)s“", "Your answer": "Vaše odpověď", "Your files on the server, as the chats see them.": "Vaše soubory na serveru, tak jak je vidí konverzace.", + "Your server recommends LLeMbas Desktop %(version)s": "Váš server doporučuje LLeMbas Desktop %(version)s", "a question": "otázka", "agent connecting…": "agent se připojuje…", "agent off": "agent vypnutý", @@ -701,6 +704,7 @@ const LEMBAS_I18N = { "Open the activity panel": "Aktivitätsbereich öffnen", "Open the chat": "Chat öffnen", "Open the page again": "Seite erneut öffnen", + "Open the release page": "Release-Seite öffnen", "Or type an answer": "Oder eine Antwort eingeben", "Outputs": "Ausgaben", "Page": "Seite", @@ -829,6 +833,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Die Zahlen zählen den eigenen Agenten des Chats mit: 1 ist der Agent allein, ohne Subagenten; Max 4 ist der Agent und bis zu 3 Subagenten. Ein Subagent läuft auf dem Modell des Chats, auf einem anderen nur, wenn Sie es im Chat nennen.", "The previous command": "Vorheriger Befehl", "The previous command (Ctrl+Shift+↑)": "Vorheriger Befehl (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "Die Version, die zur Version des Servers passt. Dies ist %(current)s.", "The reply is ready.": "Die Antwort ist fertig.", "The result is no longer there.": "Das Ergebnis ist nicht mehr da.", "The server refused this computer's link: %(why)s": "Der Server hat die Verknüpfung dieses Computers abgelehnt: %(why)s", @@ -889,6 +894,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Ihre Worte: „%(text)s“", "Your answer": "Ihre Antwort", "Your files on the server, as the chats see them.": "Ihre Dateien auf dem Server, so wie die Chats sie sehen.", + "Your server recommends LLeMbas Desktop %(version)s": "Ihr Server empfiehlt LLeMbas Desktop %(version)s", "a question": "eine Frage", "agent connecting…": "Agent verbindet…", "agent off": "Agent aus", @@ -1166,6 +1172,7 @@ const LEMBAS_I18N = { "Open the activity panel": "Abrir el panel de actividad", "Open the chat": "Abrir el chat", "Open the page again": "Abrir la página de nuevo", + "Open the release page": "Abrir la página de la versión", "Or type an answer": "O escriba una respuesta", "Outputs": "Resultados", "Page": "Página", @@ -1294,6 +1301,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Los números cuentan el agente propio del chat: 1 es el agente solo, sin subagentes; Máx. 4 es el agente y hasta 3 subagentes. Un subagente usa el modelo del chat, u otro solo si lo nombra en el chat.", "The previous command": "El comando anterior", "The previous command (Ctrl+Shift+↑)": "El comando anterior (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "La versión que corresponde a la del servidor. Esta es %(current)s.", "The reply is ready.": "La respuesta está lista.", "The result is no longer there.": "El resultado ya no está.", "The server refused this computer's link: %(why)s": "El servidor rechazó la vinculación de este equipo: %(why)s", @@ -1354,6 +1362,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Usted pidió: «%(text)s»", "Your answer": "Su respuesta", "Your files on the server, as the chats see them.": "Sus archivos en el servidor, tal como los ven los chats.", + "Your server recommends LLeMbas Desktop %(version)s": "Su servidor recomienda LLeMbas Desktop %(version)s", "a question": "una pregunta", "agent connecting…": "agente conectando…", "agent off": "agente apagado", @@ -1631,6 +1640,7 @@ const LEMBAS_I18N = { "Open the activity panel": "Ouvrir le panneau d’activité", "Open the chat": "Ouvrir la conversation", "Open the page again": "Rouvrir la page", + "Open the release page": "Ouvrir la page de la version", "Or type an answer": "Ou saisissez une réponse", "Outputs": "Sorties", "Page": "Page", @@ -1759,6 +1769,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Les nombres comptent l’agent de la conversation : 1, c’est l’agent seul, sans sous-agents ; Max 4, c’est l’agent et jusqu’à 3 sous-agents. Un sous-agent utilise le modèle de la conversation, ou un autre seulement si vous le nommez dans la conversation.", "The previous command": "La commande précédente", "The previous command (Ctrl+Shift+↑)": "La commande précédente (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "La version qui va avec celle du serveur. Celle-ci est la %(current)s.", "The reply is ready.": "La réponse est prête.", "The result is no longer there.": "Le résultat n'est plus là.", "The server refused this computer's link: %(why)s": "Le serveur a refusé la liaison de cet ordinateur : %(why)s", @@ -1819,6 +1830,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Votre demande : « %(text)s »", "Your answer": "Votre réponse", "Your files on the server, as the chats see them.": "Vos fichiers sur le serveur, tels que les conversations les voient.", + "Your server recommends LLeMbas Desktop %(version)s": "Votre serveur recommande LLeMbas Desktop %(version)s", "a question": "une question", "agent connecting…": "agent en connexion…", "agent off": "agent arrêté", @@ -2096,6 +2108,7 @@ const LEMBAS_I18N = { "Open the activity panel": "A tevékenységpanel megnyitása", "Open the chat": "A csevegés megnyitása", "Open the page again": "Oldal újbóli megnyitása", + "Open the release page": "Kiadás oldalának megnyitása", "Or type an answer": "Vagy írjon választ", "Outputs": "Kimenetek", "Page": "Oldal", @@ -2224,6 +2237,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "A számokba a beszélgetés saját ügynöke is beleszámít: az 1 maga az ügynök, alügynökök nélkül; a Max. 4 az ügynök és legfeljebb 3 alügynök. Az alügynök a beszélgetés modelljén fut, másikon csak akkor, ha megnevezi a beszélgetésben.", "The previous command": "Előző parancs", "The previous command (Ctrl+Shift+↑)": "Előző parancs (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "A szerver verziójához tartozó kiadás. Ez a %(current)s.", "The reply is ready.": "A válasz kész.", "The result is no longer there.": "Az eredmény már nincs meg.", "The server refused this computer's link: %(why)s": "A szerver elutasította ennek a számítógépnek a kapcsolatát: %(why)s", @@ -2284,6 +2298,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Az Ön kérése: „%(text)s”", "Your answer": "Az Ön válasza", "Your files on the server, as the chats see them.": "Az Ön fájljai a szerveren, ahogy a beszélgetések látják őket.", + "Your server recommends LLeMbas Desktop %(version)s": "A szervere a LLeMbas Desktop %(version)s verziót ajánlja", "a question": "egy kérdés", "agent connecting…": "ügynök csatlakozik…", "agent off": "ügynök kikapcsolva", @@ -2561,6 +2576,7 @@ const LEMBAS_I18N = { "Open the activity panel": "Apri il pannello attività", "Open the chat": "Apri la chat", "Open the page again": "Riapri la pagina", + "Open the release page": "Apri la pagina della versione", "Or type an answer": "Oppure scriva una risposta", "Outputs": "Risultati", "Page": "Pagina", @@ -2689,6 +2705,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "I numeri contano anche l’agente della chat: 1 è l’agente da solo, senza sottoagenti; Max 4 è l’agente più fino a 3 sottoagenti. Un sottoagente usa il modello della chat, o un altro solo se lo nomini nella chat.", "The previous command": "Il comando precedente", "The previous command (Ctrl+Shift+↑)": "Il comando precedente (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "La versione che va con quella del server. Questa è la %(current)s.", "The reply is ready.": "La risposta è pronta.", "The result is no longer there.": "Il risultato non c’è più.", "The server refused this computer's link: %(why)s": "Il server ha rifiutato il collegamento di questo computer: %(why)s", @@ -2749,6 +2766,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Hai chiesto: «%(text)s»", "Your answer": "La sua risposta", "Your files on the server, as the chats see them.": "I tuoi file sul server, come li vedono le chat.", + "Your server recommends LLeMbas Desktop %(version)s": "Il tuo server consiglia LLeMbas Desktop %(version)s", "a question": "una domanda", "agent connecting…": "agente in collegamento…", "agent off": "agente spento", @@ -3026,6 +3044,7 @@ const LEMBAS_I18N = { "Open the activity panel": "Otwórz panel aktywności", "Open the chat": "Otwórz czat", "Open the page again": "Otwórz stronę ponownie", + "Open the release page": "Otwórz stronę wydania", "Or type an answer": "Albo wpisz odpowiedź", "Outputs": "Wyniki", "Page": "Strona", @@ -3154,6 +3173,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Liczby obejmują własnego agenta czatu: 1 to sam agent, bez podagentów; Maks 4 to agent i do 3 podagentów. Podagent działa na modelu czatu, na innym tylko wtedy, gdy wskażesz go w czacie.", "The previous command": "Poprzednie polecenie", "The previous command (Ctrl+Shift+↑)": "Poprzednie polecenie (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "Wydanie pasujące do wersji serwera. To jest %(current)s.", "The reply is ready.": "Odpowiedź jest gotowa.", "The result is no longer there.": "Wyniku już nie ma.", "The server refused this computer's link: %(why)s": "Serwer odrzucił połączenie tego komputera: %(why)s", @@ -3214,6 +3234,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Twoja prośba: „%(text)s”", "Your answer": "Twoja odpowiedź", "Your files on the server, as the chats see them.": "Twoje pliki na serwerze, tak jak widzą je czaty.", + "Your server recommends LLeMbas Desktop %(version)s": "Twój serwer zaleca LLeMbas Desktop %(version)s", "a question": "pytanie", "agent connecting…": "agent łączy się…", "agent off": "agent wyłączony", @@ -3491,6 +3512,7 @@ const LEMBAS_I18N = { "Open the activity panel": "Otvoriť panel aktivity", "Open the chat": "Otvoriť konverzáciu", "Open the page again": "Otvoriť stránku znova", + "Open the release page": "Otvoriť stránku vydania", "Or type an answer": "Alebo napíšte odpoveď", "Outputs": "Výstupy", "Page": "Stránka", @@ -3619,6 +3641,7 @@ const LEMBAS_I18N = { "The numbers count this chat's own agent: 1 is the agent alone, with no subagents; Max 4 is the agent and up to 3 subagents. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Čísla počítajú aj vlastného agenta konverzácie: 1 je agent sám, bez podagentov; Max 4 je agent a až 3 podagenti. Podagent beží na modeli konverzácie, na inom len vtedy, keď ho v konverzácii pomenujete.", "The previous command": "Predchádzajúci príkaz", "The previous command (Ctrl+Shift+↑)": "Predchádzajúci príkaz (Ctrl+Shift+↑)", + "The release that goes with the server's version. This is %(current)s.": "Vydanie, ktoré patrí k verzii servera. Toto je %(current)s.", "The reply is ready.": "Odpoveď je hotová.", "The result is no longer there.": "Výsledok už tu nie je.", "The server refused this computer's link: %(why)s": "Server odmietol prepojenie tohto počítača: %(why)s", @@ -3679,6 +3702,7 @@ const LEMBAS_I18N = { "You asked: “%(text)s”": "Požiadali ste: „%(text)s“", "Your answer": "Vaša odpoveď", "Your files on the server, as the chats see them.": "Vaše súbory na serveri, tak ako ich vidia konverzácie.", + "Your server recommends LLeMbas Desktop %(version)s": "Váš server odporúča LLeMbas Desktop %(version)s", "a question": "otázka", "agent connecting…": "agent sa pripája…", "agent off": "agent vypnutý", diff --git a/ui/js/settings.js b/ui/js/settings.js index 77d87f0..1e2d75a 100644 --- a/ui/js/settings.js +++ b/ui/js/settings.js @@ -4,7 +4,7 @@ // this release. import { $, h, clear, icon, ICONS } from "./dom.js" -import { invoke, listen } from "./bridge.js" +import { invoke, listen, get, openLink } from "./bridge.js" import { agentBlock, onAgentChange, link } from "./agent.js" import { localTheme, chooseTheme, accountTheme, accountThemes, chosenLanguage, chooseLanguage, accountLanguage } from "./look.js" import { why } from "./bridge.js" @@ -136,6 +136,7 @@ export function renderSettings(s) { ), ) + const advised = recommendation(s) box.append( section( "about", @@ -145,8 +146,64 @@ export function renderSettings(s) { s.update ? t("LLeMbas %(version)s is available (this is %(current)s)", { version: s.update, current: s.version }) : "", h("button", { type: "button", class: "btn btn--sm", text: s.update ? t("Install LLeMbas %(version)s…", { version: s.update }) : t("Check for updates…"), onclick: () => invoke("update_app") }), ), + advised + ? line( + t("Your server recommends LLeMbas Desktop %(version)s", { version: advised.version }), + t("The release that goes with the server's version. This is %(current)s.", { current: s.version }), + h("button", { type: "button", class: "btn btn--sm", text: t("Open the release page"), onclick: () => openLink(advised.page) }), + ) + : null, ), ) + if (s.account && appsFor !== s.server) askApps(s.server) +} + +// --- The release the server recommends (GET /apps) -------------------------------------------------- +// +// GET /apps { server: { version, channel }, apps: { desktop: { version, tag, channel, page } | null } } +// +// Shown when it is newer than this app. A stable install is never offered a beta; a beta install +// may be. Only the project's own release pages are opened. + +let apps = null // the server's recommendation; null: none (an older server, or nothing newer) +let appsFor = null // the server it was asked of +const RELEASES = "https://github.com/LLeMbas/LLeMbas-Desktop/releases/tag/" +const VERSION = /^v?(\d+)\.(\d+)\.(\d+)(?:-beta\.(\d+))?$/ + +/** A sortable key for `X.Y.Z` / `X.Y.Z-beta.N` (a release above its betas); null for anything else. */ +export function versionKey(text) { + const m = VERSION.exec(String(text || "").trim()) + return m ? [+m[1], +m[2], +m[3], m[4] === undefined ? Infinity : +m[4]] : null +} + +function newer(a, b) { + for (let i = 0; i < 4; i++) if (a[i] !== b[i]) return a[i] > b[i] + return false +} + +async function askApps(server) { + appsFor = server + apps = null + try { + const r = (await get("/apps"))?.apps?.desktop || null + if (appsFor === server) apps = r + } catch { + apps = null + } + if (!$("settings").hidden) renderSettings(current()) +} + +/** The recommended release, where it is newer than this one and on a channel this install takes. */ +function recommendation(s) { + if (!apps) return null + const mine = versionKey(s.version) + const theirs = versionKey(apps.version) + if (!mine || !theirs) return null + if (theirs[3] !== Infinity && mine[3] === Infinity) return null + if (!newer(theirs, mine)) return null + const page = String(apps.page || "") + if (!page.startsWith(RELEASES) || !/^[A-Za-z0-9._-]+$/.test(page.slice(RELEASES.length))) return null + return { version: apps.version, page } } let lookError = ""