Compare commits
4
Commits
v1.0.0-beta.4
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10c82a3c7b
|
||
|
|
3510582c8f
|
||
|
|
ede91887f7
|
||
|
|
6bad293b29
|
No files matched your search
@@ -6,6 +6,34 @@ All notable changes to LLeMbas Desktop. The format follows
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.0.0-beta.5] — 2026-11-09
|
||||||
|
|
||||||
|
Carries LLeMbas CLI 2.0.0-beta.5 and the look of LLeMbas 2.0.0-beta.7, which the Subagents
|
||||||
|
setting and the panel's compaction need.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **Subagents**, by that name everywhere. The chip under the message box says **Subagents** with the
|
||||||
|
least and the most agents a chat runs ("Subagents 1–4"), and opens one slider with two handles,
|
||||||
|
**Min** and **Max**, from 1 to the server's cap -- Min never passes Max, and both move with the
|
||||||
|
arrow keys as well. The numbers count the chat's own agent: **1 is the agent alone**, with no
|
||||||
|
subagents; Max 4 is the agent and up to 3 subagents. The chip shows where the
|
||||||
|
server offers subagents for the chat (for a new chat, for its model), and the most is the
|
||||||
|
server's cap. Where the chat's model serves one request at a time, it says that subagents need
|
||||||
|
another model, on another server, named in the chat. Subagents work on the chat's own model
|
||||||
|
unless you name another one in the chat -- there is no setting for a subagent's model. A
|
||||||
|
scheduled task's field is **Subagents** too. Needs LLeMbas 2.0.0-beta.7 for the server's word on
|
||||||
|
all of this; with an older one the app guesses as before.
|
||||||
|
- **The activity panel, reshaped.** A button in the chat's header opens it at any time, and the
|
||||||
|
line above the message box shows only while something runs ("3 running · 1 queued · 2
|
||||||
|
finished"). An agent chat's **Environment** (its folder, and where it is) comes first. **Context**
|
||||||
|
says how far it is to the automatic compaction ("80.6k until auto-compact") beside a **Compact**
|
||||||
|
button that summarises the earlier turns now (greyed while there is nothing to summarise). **Background tasks** lists subagents, a team's
|
||||||
|
members and background commands under **Running**, each with how long it has run and a stop
|
||||||
|
square, unfolding to what it is, its latest step, what it is writing and its conversation; the
|
||||||
|
tasks under one fold beneath it ("Tasks: 2"), and the finished ones fold away under **Finished**.
|
||||||
|
The team's **Room** and the **Plan** have sections of their own.
|
||||||
|
|
||||||
## [1.0.0-beta.4] — 2026-11-09
|
## [1.0.0-beta.4] — 2026-11-09
|
||||||
|
|
||||||
Carries LLeMbas CLI 2.0.0-beta.3 and the look of LLeMbas 2.0.0-beta.6. Scheduled tasks need LLeMbas
|
Carries LLeMbas CLI 2.0.0-beta.3 and the look of LLeMbas 2.0.0-beta.6. Scheduled tasks need LLeMbas
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
2.0.0-beta.3
|
2.0.0-beta.5
|
||||||
+235
-35
@@ -194,10 +194,8 @@ KEYS = (
|
|||||||
"off",
|
"off",
|
||||||
"default",
|
"default",
|
||||||
"Think before answering",
|
"Think before answering",
|
||||||
"Fewest",
|
"Min",
|
||||||
"Most",
|
"Max",
|
||||||
"Agents",
|
|
||||||
"Its own agent included. Above one, it can add members that do the work while it leads them.",
|
|
||||||
"Continue in a terminal…",
|
"Continue in a terminal…",
|
||||||
"Continue in a terminal",
|
"Continue in a terminal",
|
||||||
"Run this in a terminal: the agent's session goes on there, in this chat's folder, and the chat keeps it.",
|
"Run this in a terminal: the agent's session goes on there, in this chat's folder, and the chat keeps it.",
|
||||||
@@ -207,8 +205,6 @@ KEYS = (
|
|||||||
"Back",
|
"Back",
|
||||||
"Open the activity panel",
|
"Open the activity panel",
|
||||||
"Close the activity panel",
|
"Close the activity panel",
|
||||||
"1 agent working",
|
|
||||||
"%(n)s agents working",
|
|
||||||
"%(n)s queued",
|
"%(n)s queued",
|
||||||
"%(n)s waiting",
|
"%(n)s waiting",
|
||||||
"%(n)s finished",
|
"%(n)s finished",
|
||||||
@@ -240,9 +236,7 @@ KEYS = (
|
|||||||
"Estimated: no reply has been counted yet.",
|
"Estimated: no reply has been counted yet.",
|
||||||
"%(n)s tools offered.",
|
"%(n)s tools offered.",
|
||||||
"No files written in this chat yet.",
|
"No files written in this chat yet.",
|
||||||
"Nothing runs in the background. Helpers, commands and a team's members show here while they work.",
|
|
||||||
"Nothing running.",
|
"Nothing running.",
|
||||||
"Team: %(size)s of %(max)s agents",
|
|
||||||
"Show %(n)s more",
|
"Show %(n)s more",
|
||||||
"Room",
|
"Room",
|
||||||
"report",
|
"report",
|
||||||
@@ -323,8 +317,6 @@ KEYS = (
|
|||||||
"A busy model is waited for, then the next free one is used.",
|
"A busy model is waited for, then the next free one is used.",
|
||||||
"A result, but not a run with nothing to report.",
|
"A result, but not a run with nothing to report.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.",
|
||||||
"A team of up to %(n)s",
|
|
||||||
"A team works on it together, its own agent included.",
|
|
||||||
"Choose a model to choose its effort.",
|
"Choose a model to choose its effort.",
|
||||||
"Due %(when)s",
|
"Due %(when)s",
|
||||||
"Each run sees the ones before it.",
|
"Each run sees the ones before it.",
|
||||||
@@ -333,7 +325,6 @@ KEYS = (
|
|||||||
"Edit %(title)s",
|
"Edit %(title)s",
|
||||||
"Every result, even with nothing to report.",
|
"Every result, even with nothing to report.",
|
||||||
"Models for scheduled tasks",
|
"Models for scheduled tasks",
|
||||||
"No team",
|
|
||||||
"Only in its history.",
|
"Only in its history.",
|
||||||
"Open the chat",
|
"Open the chat",
|
||||||
"Result",
|
"Result",
|
||||||
@@ -358,6 +349,31 @@ KEYS = (
|
|||||||
"Open its chat",
|
"Open its chat",
|
||||||
"The model's own",
|
"The model's own",
|
||||||
"run by hand",
|
"run by hand",
|
||||||
|
# activity.js, chat.js, models.js, scheduled.js: subagents, the panel, Compact
|
||||||
|
"%(n)s running",
|
||||||
|
"Tasks: %(n)s",
|
||||||
|
"%(tokens)s until auto-compact",
|
||||||
|
"Automatic compaction is off.",
|
||||||
|
"Compacts before the next reply.",
|
||||||
|
"Compacts by itself once the window is known.",
|
||||||
|
"Compact",
|
||||||
|
"Compacting…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.",
|
||||||
|
"Context and background tasks",
|
||||||
|
"Environment",
|
||||||
|
"Finished %(n)s",
|
||||||
|
"Background command",
|
||||||
|
"Subagent",
|
||||||
|
"Team member",
|
||||||
|
"Subagents",
|
||||||
|
"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.",
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.",
|
||||||
|
"None",
|
||||||
|
"Up to %(n)s at once",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the
|
# The app's own sentences. The mode names (Manual, Edit, Auto, Plan) stay English, as the
|
||||||
@@ -365,6 +381,32 @@ KEYS = (
|
|||||||
# the device, as the server's catalogue has it.
|
# the device, as the server's catalogue has it.
|
||||||
OWN: dict[str, dict[str, str]] = {
|
OWN: dict[str, dict[str, str]] = {
|
||||||
"cs": {
|
"cs": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Tento model vyřizuje najednou jeden požadavek: podagenti potřebují jiný model na jiném serveru, pojmenovaný v konverzaci.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Min",
|
||||||
|
"Max": "Max",
|
||||||
|
"Tasks: %(n)s": "Úlohy: %(n)s",
|
||||||
|
"%(n)s running": "Běží: %(n)s",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s do automatického shrnutí",
|
||||||
|
"Automatic compaction is off.": "Automatické shrnutí je vypnuté.",
|
||||||
|
"Compacts before the next reply.": "Shrne se před další odpovědí.",
|
||||||
|
"Compacts by itself once the window is known.": "Shrne se samo, jakmile bude známé okno.",
|
||||||
|
"Compact": "Shrnout",
|
||||||
|
"Compacting…": "Shrnuje se…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "Shrnout dřívější kola hned a posílat místo nich shrnutí.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Tento server zatím neumí shrnout konverzaci z aplikace; webové rozhraní to umí.",
|
||||||
|
"Context and background tasks": "Kontext a úlohy na pozadí",
|
||||||
|
"Environment": "Prostředí",
|
||||||
|
"Finished %(n)s": "Dokončeno: %(n)s",
|
||||||
|
"Background command": "Příkaz na pozadí",
|
||||||
|
"Subagent": "Podagent",
|
||||||
|
"Team member": "Člen týmu",
|
||||||
|
"Subagents": "Podagenti",
|
||||||
|
"None": "Žádní",
|
||||||
|
"Up to %(n)s at once": "Nejvýše %(n)s současně",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Na pozadí nic neběží. Podagenti, členové týmu a příkazy na pozadí se tu zobrazí, dokud pracují.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Tým: %(size)s z nejvýše %(max)s podagentů",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Kolik jich na ní pracuje současně, včetně vlastního agenta běhu, na stejném modelu.",
|
||||||
"Menu": "Nabídka",
|
"Menu": "Nabídka",
|
||||||
"Minimise": "Minimalizovat",
|
"Minimise": "Minimalizovat",
|
||||||
"Maximise": "Maximalizovat",
|
"Maximise": "Maximalizovat",
|
||||||
@@ -532,8 +574,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "Na zaneprázdněný model se čeká, pak se použije další volný.",
|
"A busy model is waited for, then the next free one is used.": "Na zaneprázdněný model se čeká, pak se použije další volný.",
|
||||||
"A result, but not a run with nothing to report.": "Výsledek, ale ne spuštění, které nemá co hlásit.",
|
"A result, but not a run with nothing to report.": "Výsledek, ale ne spuštění, které nemá co hlásit.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastního modelu běží na prvním volném z těchto, v tomto pořadí. Žádný nezaškrtnutý: všechny.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastního modelu běží na prvním volném z těchto, v tomto pořadí. Žádný nezaškrtnutý: všechny.",
|
||||||
"A team of up to %(n)s": "Tým: nejvýše %(n)s",
|
|
||||||
"A team works on it together, its own agent included.": "Pracuje na ní tým společně, včetně jejího vlastního agenta.",
|
|
||||||
"Choose a model to choose its effort.": "Vyberte model, abyste mohli zvolit jeho náročnost.",
|
"Choose a model to choose its effort.": "Vyberte model, abyste mohli zvolit jeho náročnost.",
|
||||||
"Due %(when)s": "Naplánováno na %(when)s",
|
"Due %(when)s": "Naplánováno na %(when)s",
|
||||||
"Each run sees the ones before it.": "Každé spuštění vidí ta předchozí.",
|
"Each run sees the ones before it.": "Každé spuštění vidí ta předchozí.",
|
||||||
@@ -542,7 +582,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "Upravit %(title)s",
|
"Edit %(title)s": "Upravit %(title)s",
|
||||||
"Every result, even with nothing to report.": "Každý výsledek, i když není co hlásit.",
|
"Every result, even with nothing to report.": "Každý výsledek, i když není co hlásit.",
|
||||||
"Models for scheduled tasks": "Modely pro naplánované úlohy",
|
"Models for scheduled tasks": "Modely pro naplánované úlohy",
|
||||||
"No team": "Bez týmu",
|
|
||||||
"Only in its history.": "Jen v její historii.",
|
"Only in its history.": "Jen v její historii.",
|
||||||
"Open the chat": "Otevřít konverzaci",
|
"Open the chat": "Otevřít konverzaci",
|
||||||
"Result": "Výsledek",
|
"Result": "Výsledek",
|
||||||
@@ -563,6 +602,32 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"No day ticked: every day.": "Žádný den nezaškrtnutý: každý den.",
|
"No day ticked: every day.": "Žádný den nezaškrtnutý: každý den.",
|
||||||
},
|
},
|
||||||
"de": {
|
"de": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Dieses Modell bedient eine Anfrage zur Zeit: Subagenten brauchen ein anderes Modell auf einem anderen Server, im Chat genannt.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Min",
|
||||||
|
"Max": "Max",
|
||||||
|
"Tasks: %(n)s": "Aufgaben: %(n)s",
|
||||||
|
"%(n)s running": "%(n)s laufen",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s bis zur automatischen Zusammenfassung",
|
||||||
|
"Automatic compaction is off.": "Die automatische Zusammenfassung ist aus.",
|
||||||
|
"Compacts before the next reply.": "Wird vor der nächsten Antwort zusammengefasst.",
|
||||||
|
"Compacts by itself once the window is known.": "Wird von selbst zusammengefasst, sobald das Fenster bekannt ist.",
|
||||||
|
"Compact": "Zusammenfassen",
|
||||||
|
"Compacting…": "Wird zusammengefasst…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "Die früheren Runden jetzt zusammenfassen und stattdessen die Zusammenfassung senden.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Dieser Server kann einen Chat noch nicht aus der App zusammenfassen; die Weboberfläche kann es.",
|
||||||
|
"Context and background tasks": "Kontext und Hintergrundaufgaben",
|
||||||
|
"Environment": "Umgebung",
|
||||||
|
"Finished %(n)s": "Fertig: %(n)s",
|
||||||
|
"Background command": "Hintergrundbefehl",
|
||||||
|
"Subagent": "Subagent",
|
||||||
|
"Team member": "Teammitglied",
|
||||||
|
"Subagents": "Subagenten",
|
||||||
|
"None": "Keine",
|
||||||
|
"Up to %(n)s at once": "Bis zu %(n)s gleichzeitig",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Im Hintergrund läuft nichts. Subagenten, die Mitglieder eines Teams und Hintergrundbefehle erscheinen hier, solange sie arbeiten.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Team: %(size)s von höchstens %(max)s Subagenten",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Wie viele gleichzeitig daran arbeiten, der eigene Agent des Laufs mitgezählt, mit demselben Modell.",
|
||||||
"Menu": "Menü",
|
"Menu": "Menü",
|
||||||
"Minimise": "Minimieren",
|
"Minimise": "Minimieren",
|
||||||
"Maximise": "Maximieren",
|
"Maximise": "Maximieren",
|
||||||
@@ -730,8 +795,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "Auf ein belegtes Modell wird gewartet, dann wird das nächste freie genommen.",
|
"A busy model is waited for, then the next free one is used.": "Auf ein belegtes Modell wird gewartet, dann wird das nächste freie genommen.",
|
||||||
"A result, but not a run with nothing to report.": "Ein Ergebnis, aber kein Lauf ohne Neuigkeiten.",
|
"A result, but not a run with nothing to report.": "Ein Ergebnis, aber kein Lauf ohne Neuigkeiten.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Eine Aufgabe ohne eigenes Modell läuft auf dem ersten freien davon, in dieser Reihenfolge. Keines angehakt: alle.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Eine Aufgabe ohne eigenes Modell läuft auf dem ersten freien davon, in dieser Reihenfolge. Keines angehakt: alle.",
|
||||||
"A team of up to %(n)s": "Ein Team mit bis zu %(n)s",
|
|
||||||
"A team works on it together, its own agent included.": "Ein Team arbeitet gemeinsam daran, ihr eigener Agent eingeschlossen.",
|
|
||||||
"Choose a model to choose its effort.": "Wählen Sie ein Modell, um seinen Aufwand zu wählen.",
|
"Choose a model to choose its effort.": "Wählen Sie ein Modell, um seinen Aufwand zu wählen.",
|
||||||
"Due %(when)s": "Fällig %(when)s",
|
"Due %(when)s": "Fällig %(when)s",
|
||||||
"Each run sees the ones before it.": "Jeder Lauf sieht die vorherigen.",
|
"Each run sees the ones before it.": "Jeder Lauf sieht die vorherigen.",
|
||||||
@@ -740,7 +803,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "%(title)s bearbeiten",
|
"Edit %(title)s": "%(title)s bearbeiten",
|
||||||
"Every result, even with nothing to report.": "Jedes Ergebnis, auch ohne Neuigkeiten.",
|
"Every result, even with nothing to report.": "Jedes Ergebnis, auch ohne Neuigkeiten.",
|
||||||
"Models for scheduled tasks": "Modelle für geplante Aufgaben",
|
"Models for scheduled tasks": "Modelle für geplante Aufgaben",
|
||||||
"No team": "Kein Team",
|
|
||||||
"Only in its history.": "Nur in ihrem Verlauf.",
|
"Only in its history.": "Nur in ihrem Verlauf.",
|
||||||
"Open the chat": "Chat öffnen",
|
"Open the chat": "Chat öffnen",
|
||||||
"Result": "Ergebnis",
|
"Result": "Ergebnis",
|
||||||
@@ -761,6 +823,32 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"No day ticked: every day.": "Kein Tag angehakt: jeden Tag.",
|
"No day ticked: every day.": "Kein Tag angehakt: jeden Tag.",
|
||||||
},
|
},
|
||||||
"es": {
|
"es": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Este modelo atiende una petición a la vez: los subagentes necesitan otro modelo, en otro servidor, nombrado en el chat.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Mín.",
|
||||||
|
"Max": "Máx.",
|
||||||
|
"Tasks: %(n)s": "Tareas: %(n)s",
|
||||||
|
"%(n)s running": "%(n)s en marcha",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s hasta la compactación automática",
|
||||||
|
"Automatic compaction is off.": "La compactación automática está desactivada.",
|
||||||
|
"Compacts before the next reply.": "Se compacta antes de la próxima respuesta.",
|
||||||
|
"Compacts by itself once the window is known.": "Se compacta sola en cuanto se conozca la ventana.",
|
||||||
|
"Compact": "Compactar",
|
||||||
|
"Compacting…": "Compactando…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "Resumir ahora los turnos anteriores y enviar el resumen en su lugar.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Este servidor aún no puede compactar un chat desde la aplicación; la interfaz web sí puede.",
|
||||||
|
"Context and background tasks": "Contexto y tareas en segundo plano",
|
||||||
|
"Environment": "Entorno",
|
||||||
|
"Finished %(n)s": "Terminadas: %(n)s",
|
||||||
|
"Background command": "Comando en segundo plano",
|
||||||
|
"Subagent": "Subagente",
|
||||||
|
"Team member": "Miembro del equipo",
|
||||||
|
"Subagents": "Subagentes",
|
||||||
|
"None": "Ninguno",
|
||||||
|
"Up to %(n)s at once": "Hasta %(n)s a la vez",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "No hay nada en segundo plano. Los subagentes, los miembros de un equipo y los comandos en segundo plano aparecen aquí mientras trabajan.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Equipo: %(size)s de un máximo de %(max)s subagentes",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Cuántos trabajan en ella a la vez, contando el propio agente de la ejecución, con el mismo modelo.",
|
||||||
"Menu": "Menú",
|
"Menu": "Menú",
|
||||||
"Minimise": "Minimizar",
|
"Minimise": "Minimizar",
|
||||||
"Maximise": "Maximizar",
|
"Maximise": "Maximizar",
|
||||||
@@ -928,8 +1016,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "Se espera a un modelo ocupado y luego se usa el siguiente libre.",
|
"A busy model is waited for, then the next free one is used.": "Se espera a un modelo ocupado y luego se usa el siguiente libre.",
|
||||||
"A result, but not a run with nothing to report.": "Un resultado, pero no una ejecución sin nada que informar.",
|
"A result, but not a run with nothing to report.": "Un resultado, pero no una ejecución sin nada que informar.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Una tarea sin modelo propio se ejecuta en el primero libre de estos, en este orden. Ninguno marcado: todos.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Una tarea sin modelo propio se ejecuta en el primero libre de estos, en este orden. Ninguno marcado: todos.",
|
||||||
"A team of up to %(n)s": "Un equipo de hasta %(n)s",
|
|
||||||
"A team works on it together, its own agent included.": "Un equipo trabaja en ella, incluido su propio agente.",
|
|
||||||
"Choose a model to choose its effort.": "Elija un modelo para elegir su esfuerzo.",
|
"Choose a model to choose its effort.": "Elija un modelo para elegir su esfuerzo.",
|
||||||
"Due %(when)s": "Prevista %(when)s",
|
"Due %(when)s": "Prevista %(when)s",
|
||||||
"Each run sees the ones before it.": "Cada ejecución ve las anteriores.",
|
"Each run sees the ones before it.": "Cada ejecución ve las anteriores.",
|
||||||
@@ -938,7 +1024,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "Editar %(title)s",
|
"Edit %(title)s": "Editar %(title)s",
|
||||||
"Every result, even with nothing to report.": "Cada resultado, incluso sin nada que informar.",
|
"Every result, even with nothing to report.": "Cada resultado, incluso sin nada que informar.",
|
||||||
"Models for scheduled tasks": "Modelos para tareas programadas",
|
"Models for scheduled tasks": "Modelos para tareas programadas",
|
||||||
"No team": "Sin equipo",
|
|
||||||
"Only in its history.": "Solo en su historial.",
|
"Only in its history.": "Solo en su historial.",
|
||||||
"Open the chat": "Abrir el chat",
|
"Open the chat": "Abrir el chat",
|
||||||
"Result": "Resultado",
|
"Result": "Resultado",
|
||||||
@@ -959,6 +1044,32 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"No day ticked: every day.": "Ningún día marcado: todos los días.",
|
"No day ticked: every day.": "Ningún día marcado: todos los días.",
|
||||||
},
|
},
|
||||||
"fr": {
|
"fr": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Ce modèle traite une requête à la fois : les sous-agents ont besoin d’un autre modèle, sur un autre serveur, nommé dans la conversation.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Min",
|
||||||
|
"Max": "Max",
|
||||||
|
"Tasks: %(n)s": "Tâches : %(n)s",
|
||||||
|
"%(n)s running": "%(n)s en cours",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s avant le compactage automatique",
|
||||||
|
"Automatic compaction is off.": "Le compactage automatique est désactivé.",
|
||||||
|
"Compacts before the next reply.": "Compacté avant la prochaine réponse.",
|
||||||
|
"Compacts by itself once the window is known.": "Compacté de lui-même dès que la fenêtre est connue.",
|
||||||
|
"Compact": "Compacter",
|
||||||
|
"Compacting…": "Compactage…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "Résumer maintenant les tours précédents et envoyer le résumé à leur place.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Ce serveur ne peut pas encore compacter une conversation depuis l’application ; l’interface web le peut.",
|
||||||
|
"Context and background tasks": "Contexte et tâches en arrière-plan",
|
||||||
|
"Environment": "Environnement",
|
||||||
|
"Finished %(n)s": "Terminées : %(n)s",
|
||||||
|
"Background command": "Commande en arrière-plan",
|
||||||
|
"Subagent": "Sous-agent",
|
||||||
|
"Team member": "Membre de l’équipe",
|
||||||
|
"Subagents": "Sous-agents",
|
||||||
|
"None": "Aucun",
|
||||||
|
"Up to %(n)s at once": "Jusqu’à %(n)s à la fois",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Rien ne tourne en arrière-plan. Les sous-agents, les membres d’une équipe et les commandes en arrière-plan s’affichent ici pendant qu’ils travaillent.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Équipe : %(size)s sur %(max)s sous-agents au plus",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Combien y travaillent à la fois, l’agent de l’exécution compris, avec le même modèle.",
|
||||||
"Menu": "Menu",
|
"Menu": "Menu",
|
||||||
"Minimise": "Réduire",
|
"Minimise": "Réduire",
|
||||||
"Maximise": "Agrandir",
|
"Maximise": "Agrandir",
|
||||||
@@ -1126,8 +1237,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "Un modèle occupé est attendu, puis le suivant libre est utilisé.",
|
"A busy model is waited for, then the next free one is used.": "Un modèle occupé est attendu, puis le suivant libre est utilisé.",
|
||||||
"A result, but not a run with nothing to report.": "Un résultat, mais pas une exécution sans rien à signaler.",
|
"A result, but not a run with nothing to report.": "Un résultat, mais pas une exécution sans rien à signaler.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Une tâche sans modèle à elle tourne sur le premier libre de ceux-ci, dans cet ordre. Aucun coché : tous.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Une tâche sans modèle à elle tourne sur le premier libre de ceux-ci, dans cet ordre. Aucun coché : tous.",
|
||||||
"A team of up to %(n)s": "Une équipe jusqu'à %(n)s",
|
|
||||||
"A team works on it together, its own agent included.": "Une équipe y travaille ensemble, son propre agent compris.",
|
|
||||||
"Choose a model to choose its effort.": "Choisissez un modèle pour choisir son effort.",
|
"Choose a model to choose its effort.": "Choisissez un modèle pour choisir son effort.",
|
||||||
"Due %(when)s": "Prévue %(when)s",
|
"Due %(when)s": "Prévue %(when)s",
|
||||||
"Each run sees the ones before it.": "Chaque exécution voit les précédentes.",
|
"Each run sees the ones before it.": "Chaque exécution voit les précédentes.",
|
||||||
@@ -1136,7 +1245,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "Modifier %(title)s",
|
"Edit %(title)s": "Modifier %(title)s",
|
||||||
"Every result, even with nothing to report.": "Chaque résultat, même sans rien à signaler.",
|
"Every result, even with nothing to report.": "Chaque résultat, même sans rien à signaler.",
|
||||||
"Models for scheduled tasks": "Modèles pour les tâches planifiées",
|
"Models for scheduled tasks": "Modèles pour les tâches planifiées",
|
||||||
"No team": "Pas d'équipe",
|
|
||||||
"Only in its history.": "Seulement dans son historique.",
|
"Only in its history.": "Seulement dans son historique.",
|
||||||
"Open the chat": "Ouvrir la conversation",
|
"Open the chat": "Ouvrir la conversation",
|
||||||
"Result": "Résultat",
|
"Result": "Résultat",
|
||||||
@@ -1157,6 +1265,32 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"No day ticked: every day.": "Aucun jour coché : tous les jours.",
|
"No day ticked: every day.": "Aucun jour coché : tous les jours.",
|
||||||
},
|
},
|
||||||
"hu": {
|
"hu": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Ez a modell egyszerre egy kérést szolgál ki: az alügynököknek másik modell kell, másik szerveren, a beszélgetésben megnevezve.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Min.",
|
||||||
|
"Max": "Max.",
|
||||||
|
"Tasks: %(n)s": "Feladatok: %(n)s",
|
||||||
|
"%(n)s running": "%(n)s fut",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s az automatikus tömörítésig",
|
||||||
|
"Automatic compaction is off.": "Az automatikus tömörítés ki van kapcsolva.",
|
||||||
|
"Compacts before the next reply.": "A következő válasz előtt tömörül.",
|
||||||
|
"Compacts by itself once the window is known.": "Magától tömörül, amint ismert az ablak.",
|
||||||
|
"Compact": "Tömörítés",
|
||||||
|
"Compacting…": "Tömörítés…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "A korábbi köröket most összefoglalja, és helyettük az összefoglalót küldi.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Ez a szerver még nem tud beszélgetést tömöríteni az alkalmazásból; a webes felület tud.",
|
||||||
|
"Context and background tasks": "Kontextus és háttérfeladatok",
|
||||||
|
"Environment": "Környezet",
|
||||||
|
"Finished %(n)s": "Kész: %(n)s",
|
||||||
|
"Background command": "Háttérparancs",
|
||||||
|
"Subagent": "Alügynök",
|
||||||
|
"Team member": "Csapattag",
|
||||||
|
"Subagents": "Alügynökök",
|
||||||
|
"None": "Nincs",
|
||||||
|
"Up to %(n)s at once": "Legfeljebb %(n)s egyszerre",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "A háttérben semmi sem fut. Az alügynökök, egy csapat tagjai és a háttérparancsok itt jelennek meg, amíg dolgoznak.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Csapat: %(size)s a legfeljebb %(max)s alügynökből",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Hányan dolgoznak rajta egyszerre, a futás saját ügynökét is beleértve, ugyanazon a modellen.",
|
||||||
"Menu": "Menü",
|
"Menu": "Menü",
|
||||||
"Minimise": "Kis méret",
|
"Minimise": "Kis méret",
|
||||||
"Maximise": "Teljes méret",
|
"Maximise": "Teljes méret",
|
||||||
@@ -1324,8 +1458,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "A foglalt modellre vár, majd a következő szabadot használja.",
|
"A busy model is waited for, then the next free one is used.": "A foglalt modellre vár, majd a következő szabadot használja.",
|
||||||
"A result, but not a run with nothing to report.": "Eredmény, de nem olyan futás, amelynek nincs mit jelentenie.",
|
"A result, but not a run with nothing to report.": "Eredmény, de nem olyan futás, amelynek nincs mit jelentenie.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "A saját modell nélküli feladat ezek közül az első szabadon fut, ebben a sorrendben. Ha egy sincs bejelölve: mindegyiken.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "A saját modell nélküli feladat ezek közül az első szabadon fut, ebben a sorrendben. Ha egy sincs bejelölve: mindegyiken.",
|
||||||
"A team of up to %(n)s": "Legfeljebb %(n)s fős csapat",
|
|
||||||
"A team works on it together, its own agent included.": "Egy csapat dolgozik rajta együtt, a saját ügynökét is beleértve.",
|
|
||||||
"Choose a model to choose its effort.": "Válasszon modellt az erőfeszítés megadásához.",
|
"Choose a model to choose its effort.": "Válasszon modellt az erőfeszítés megadásához.",
|
||||||
"Due %(when)s": "Esedékes: %(when)s",
|
"Due %(when)s": "Esedékes: %(when)s",
|
||||||
"Each run sees the ones before it.": "Minden futás látja az előzőeket.",
|
"Each run sees the ones before it.": "Minden futás látja az előzőeket.",
|
||||||
@@ -1334,7 +1466,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "%(title)s szerkesztése",
|
"Edit %(title)s": "%(title)s szerkesztése",
|
||||||
"Every result, even with nothing to report.": "Minden eredmény, akkor is, ha nincs mit jelenteni.",
|
"Every result, even with nothing to report.": "Minden eredmény, akkor is, ha nincs mit jelenteni.",
|
||||||
"Models for scheduled tasks": "Modellek az ütemezett feladatokhoz",
|
"Models for scheduled tasks": "Modellek az ütemezett feladatokhoz",
|
||||||
"No team": "Nincs csapat",
|
|
||||||
"Only in its history.": "Csak az előzményeiben.",
|
"Only in its history.": "Csak az előzményeiben.",
|
||||||
"Open the chat": "A csevegés megnyitása",
|
"Open the chat": "A csevegés megnyitása",
|
||||||
"Result": "Eredmény",
|
"Result": "Eredmény",
|
||||||
@@ -1355,6 +1486,32 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"No day ticked: every day.": "Ha egy nap sincs bejelölve: minden nap.",
|
"No day ticked: every day.": "Ha egy nap sincs bejelölve: minden nap.",
|
||||||
},
|
},
|
||||||
"it": {
|
"it": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Questo modello serve una richiesta alla volta: i sottoagenti hanno bisogno di un altro modello, su un altro server, nominato nella chat.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Min",
|
||||||
|
"Max": "Max",
|
||||||
|
"Tasks: %(n)s": "Attività: %(n)s",
|
||||||
|
"%(n)s running": "%(n)s in corso",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s alla compattazione automatica",
|
||||||
|
"Automatic compaction is off.": "La compattazione automatica è disattivata.",
|
||||||
|
"Compacts before the next reply.": "Si compatta prima della prossima risposta.",
|
||||||
|
"Compacts by itself once the window is known.": "Si compatta da sola appena la finestra è nota.",
|
||||||
|
"Compact": "Compatta",
|
||||||
|
"Compacting…": "Compattazione…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "Riassumi ora i turni precedenti e invia il riassunto al loro posto.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Questo server non può ancora compattare una chat dall’app; l’interfaccia web sì.",
|
||||||
|
"Context and background tasks": "Contesto e attività in background",
|
||||||
|
"Environment": "Ambiente",
|
||||||
|
"Finished %(n)s": "Terminate: %(n)s",
|
||||||
|
"Background command": "Comando in background",
|
||||||
|
"Subagent": "Sottoagente",
|
||||||
|
"Team member": "Membro della squadra",
|
||||||
|
"Subagents": "Sottoagenti",
|
||||||
|
"None": "Nessuno",
|
||||||
|
"Up to %(n)s at once": "Fino a %(n)s alla volta",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Nulla è in esecuzione in background. Sottoagenti, membri di una squadra e comandi in background compaiono qui mentre lavorano.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Squadra: %(size)s su un massimo di %(max)s sottoagenti",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Quanti ci lavorano alla volta, compreso l’agente dell’esecuzione, sullo stesso modello.",
|
||||||
"Menu": "Menu",
|
"Menu": "Menu",
|
||||||
"Minimise": "Riduci a icona",
|
"Minimise": "Riduci a icona",
|
||||||
"Maximise": "Ingrandisci",
|
"Maximise": "Ingrandisci",
|
||||||
@@ -1522,8 +1679,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "Si attende un modello occupato, poi si usa il successivo libero.",
|
"A busy model is waited for, then the next free one is used.": "Si attende un modello occupato, poi si usa il successivo libero.",
|
||||||
"A result, but not a run with nothing to report.": "Un risultato, ma non un’esecuzione senza nulla da segnalare.",
|
"A result, but not a run with nothing to report.": "Un risultato, ma non un’esecuzione senza nulla da segnalare.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Un’attività senza un modello proprio gira sul primo libero di questi, in quest’ordine. Nessuno spuntato: tutti.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Un’attività senza un modello proprio gira sul primo libero di questi, in quest’ordine. Nessuno spuntato: tutti.",
|
||||||
"A team of up to %(n)s": "Un team fino a %(n)s",
|
|
||||||
"A team works on it together, its own agent included.": "Un team ci lavora insieme, incluso il suo agente.",
|
|
||||||
"Choose a model to choose its effort.": "Scegli un modello per sceglierne l’impegno.",
|
"Choose a model to choose its effort.": "Scegli un modello per sceglierne l’impegno.",
|
||||||
"Due %(when)s": "Prevista %(when)s",
|
"Due %(when)s": "Prevista %(when)s",
|
||||||
"Each run sees the ones before it.": "Ogni esecuzione vede quelle precedenti.",
|
"Each run sees the ones before it.": "Ogni esecuzione vede quelle precedenti.",
|
||||||
@@ -1532,7 +1687,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "Modifica %(title)s",
|
"Edit %(title)s": "Modifica %(title)s",
|
||||||
"Every result, even with nothing to report.": "Ogni risultato, anche senza nulla da segnalare.",
|
"Every result, even with nothing to report.": "Ogni risultato, anche senza nulla da segnalare.",
|
||||||
"Models for scheduled tasks": "Modelli per le attività pianificate",
|
"Models for scheduled tasks": "Modelli per le attività pianificate",
|
||||||
"No team": "Nessun team",
|
|
||||||
"Only in its history.": "Solo nella sua cronologia.",
|
"Only in its history.": "Solo nella sua cronologia.",
|
||||||
"Open the chat": "Apri la chat",
|
"Open the chat": "Apri la chat",
|
||||||
"Result": "Risultato",
|
"Result": "Risultato",
|
||||||
@@ -1553,6 +1707,32 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"No day ticked: every day.": "Nessun giorno spuntato: ogni giorno.",
|
"No day ticked: every day.": "Nessun giorno spuntato: ogni giorno.",
|
||||||
},
|
},
|
||||||
"pl": {
|
"pl": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Ten model obsługuje jedno żądanie naraz: podagenci potrzebują innego modelu, na innym serwerze, wskazanego w czacie.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Min",
|
||||||
|
"Max": "Maks",
|
||||||
|
"Tasks: %(n)s": "Zadania: %(n)s",
|
||||||
|
"%(n)s running": "Działa: %(n)s",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s do automatycznego streszczenia",
|
||||||
|
"Automatic compaction is off.": "Automatyczne streszczanie jest wyłączone.",
|
||||||
|
"Compacts before the next reply.": "Zostanie streszczona przed następną odpowiedzią.",
|
||||||
|
"Compacts by itself once the window is known.": "Zostanie streszczona sama, gdy okno będzie znane.",
|
||||||
|
"Compact": "Streść",
|
||||||
|
"Compacting…": "Streszczanie…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "Streścić teraz wcześniejsze tury i wysyłać streszczenie zamiast nich.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Ten serwer nie umie jeszcze streścić czatu z aplikacji; interfejs WWW umie.",
|
||||||
|
"Context and background tasks": "Kontekst i zadania w tle",
|
||||||
|
"Environment": "Środowisko",
|
||||||
|
"Finished %(n)s": "Zakończone: %(n)s",
|
||||||
|
"Background command": "Polecenie w tle",
|
||||||
|
"Subagent": "Podagent",
|
||||||
|
"Team member": "Członek zespołu",
|
||||||
|
"Subagents": "Podagenci",
|
||||||
|
"None": "Brak",
|
||||||
|
"Up to %(n)s at once": "Do %(n)s naraz",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "W tle nic nie działa. Podagenci, członkowie zespołu i polecenia w tle pojawiają się tu, gdy pracują.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Zespół: %(size)s z najwyżej %(max)s podagentów",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Ilu pracuje nad nim naraz, łącznie z własnym agentem uruchomienia, na tym samym modelu.",
|
||||||
"Menu": "Menu",
|
"Menu": "Menu",
|
||||||
"Minimise": "Minimalizuj",
|
"Minimise": "Minimalizuj",
|
||||||
"Maximise": "Maksymalizuj",
|
"Maximise": "Maksymalizuj",
|
||||||
@@ -1720,8 +1900,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "Na zajęty model się czeka, potem używany jest następny wolny.",
|
"A busy model is waited for, then the next free one is used.": "Na zajęty model się czeka, potem używany jest następny wolny.",
|
||||||
"A result, but not a run with nothing to report.": "Wynik, ale nie uruchomienie bez niczego do zgłoszenia.",
|
"A result, but not a run with nothing to report.": "Wynik, ale nie uruchomienie bez niczego do zgłoszenia.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Zadanie bez własnego modelu działa na pierwszym wolnym z tych, w tej kolejności. Żaden niezaznaczony: wszystkie.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Zadanie bez własnego modelu działa na pierwszym wolnym z tych, w tej kolejności. Żaden niezaznaczony: wszystkie.",
|
||||||
"A team of up to %(n)s": "Zespół do %(n)s",
|
|
||||||
"A team works on it together, its own agent included.": "Pracuje nad nim zespół, wraz z jego własnym agentem.",
|
|
||||||
"Choose a model to choose its effort.": "Wybierz model, aby wybrać jego wysiłek.",
|
"Choose a model to choose its effort.": "Wybierz model, aby wybrać jego wysiłek.",
|
||||||
"Due %(when)s": "Zaplanowane na %(when)s",
|
"Due %(when)s": "Zaplanowane na %(when)s",
|
||||||
"Each run sees the ones before it.": "Każde uruchomienie widzi poprzednie.",
|
"Each run sees the ones before it.": "Każde uruchomienie widzi poprzednie.",
|
||||||
@@ -1730,7 +1908,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "Edytuj %(title)s",
|
"Edit %(title)s": "Edytuj %(title)s",
|
||||||
"Every result, even with nothing to report.": "Każdy wynik, nawet bez niczego do zgłoszenia.",
|
"Every result, even with nothing to report.": "Każdy wynik, nawet bez niczego do zgłoszenia.",
|
||||||
"Models for scheduled tasks": "Modele dla zaplanowanych zadań",
|
"Models for scheduled tasks": "Modele dla zaplanowanych zadań",
|
||||||
"No team": "Bez zespołu",
|
|
||||||
"Only in its history.": "Tylko w jego historii.",
|
"Only in its history.": "Tylko w jego historii.",
|
||||||
"Open the chat": "Otwórz czat",
|
"Open the chat": "Otwórz czat",
|
||||||
"Result": "Wynik",
|
"Result": "Wynik",
|
||||||
@@ -1751,6 +1928,32 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"No day ticked: every day.": "Żaden dzień niezaznaczony: codziennie.",
|
"No day ticked: every day.": "Żaden dzień niezaznaczony: codziennie.",
|
||||||
},
|
},
|
||||||
"sk": {
|
"sk": {
|
||||||
|
"This model serves one request at a time: subagents need another model, on another server, named in the chat.": "Tento model vybavuje naraz jednu požiadavku: podagenti potrebujú iný model na inom serveri, pomenovaný v konverzácii.",
|
||||||
|
"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.",
|
||||||
|
"Min": "Min",
|
||||||
|
"Max": "Max",
|
||||||
|
"Tasks: %(n)s": "Úlohy: %(n)s",
|
||||||
|
"%(n)s running": "Beží: %(n)s",
|
||||||
|
"%(tokens)s until auto-compact": "%(tokens)s do automatického zhrnutia",
|
||||||
|
"Automatic compaction is off.": "Automatické zhrnutie je vypnuté.",
|
||||||
|
"Compacts before the next reply.": "Zhrnie sa pred ďalšou odpoveďou.",
|
||||||
|
"Compacts by itself once the window is known.": "Zhrnie sa samo, keď bude okno známe.",
|
||||||
|
"Compact": "Zhrnúť",
|
||||||
|
"Compacting…": "Zhŕňa sa…",
|
||||||
|
"Summarise the earlier turns now and send the summary in their place.": "Zhrnúť skoršie kolá hneď a posielať namiesto nich zhrnutie.",
|
||||||
|
"This server cannot compact a chat from the app yet; the web interface can.": "Tento server zatiaľ nevie zhrnúť konverzáciu z aplikácie; webové rozhranie to vie.",
|
||||||
|
"Context and background tasks": "Kontext a úlohy na pozadí",
|
||||||
|
"Environment": "Prostredie",
|
||||||
|
"Finished %(n)s": "Dokončené: %(n)s",
|
||||||
|
"Background command": "Príkaz na pozadí",
|
||||||
|
"Subagent": "Podagent",
|
||||||
|
"Team member": "Člen tímu",
|
||||||
|
"Subagents": "Podagenti",
|
||||||
|
"None": "Žiadni",
|
||||||
|
"Up to %(n)s at once": "Najviac %(n)s naraz",
|
||||||
|
"Nothing runs in the background. Subagents, a team's members and background commands show here while they work.": "Na pozadí nič nebeží. Podagenti, členovia tímu a príkazy na pozadí sa tu zobrazia, kým pracujú.",
|
||||||
|
"Team: %(size)s of at most %(max)s subagents": "Tím: %(size)s z najviac %(max)s podagentov",
|
||||||
|
"How many work on it at once, the run's own agent among them, on the same model.": "Koľko na nej pracuje naraz, vrátane vlastného agenta behu, na rovnakom modeli.",
|
||||||
"Menu": "Ponuka",
|
"Menu": "Ponuka",
|
||||||
"Minimise": "Minimalizovať",
|
"Minimise": "Minimalizovať",
|
||||||
"Maximise": "Maximalizovať",
|
"Maximise": "Maximalizovať",
|
||||||
@@ -1918,8 +2121,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"A busy model is waited for, then the next free one is used.": "Na zaneprázdnený model sa čaká, potom sa použije ďalší voľný.",
|
"A busy model is waited for, then the next free one is used.": "Na zaneprázdnený model sa čaká, potom sa použije ďalší voľný.",
|
||||||
"A result, but not a run with nothing to report.": "Výsledok, ale nie spustenie, ktoré nemá čo hlásiť.",
|
"A result, but not a run with nothing to report.": "Výsledok, ale nie spustenie, ktoré nemá čo hlásiť.",
|
||||||
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastného modelu beží na prvom voľnom z týchto, v tomto poradí. Žiadny nezaškrtnutý: všetky.",
|
"A task without a model of its own runs on the first free one of these, in this order. None ticked: all of them.": "Úloha bez vlastného modelu beží na prvom voľnom z týchto, v tomto poradí. Žiadny nezaškrtnutý: všetky.",
|
||||||
"A team of up to %(n)s": "Tím: najviac %(n)s",
|
|
||||||
"A team works on it together, its own agent included.": "Pracuje na nej tím spoločne, vrátane jej vlastného agenta.",
|
|
||||||
"Choose a model to choose its effort.": "Vyberte model, aby ste mohli zvoliť jeho náročnosť.",
|
"Choose a model to choose its effort.": "Vyberte model, aby ste mohli zvoliť jeho náročnosť.",
|
||||||
"Due %(when)s": "Naplánované na %(when)s",
|
"Due %(when)s": "Naplánované na %(when)s",
|
||||||
"Each run sees the ones before it.": "Každé spustenie vidí tie predchádzajúce.",
|
"Each run sees the ones before it.": "Každé spustenie vidí tie predchádzajúce.",
|
||||||
@@ -1928,7 +2129,6 @@ OWN: dict[str, dict[str, str]] = {
|
|||||||
"Edit %(title)s": "Upraviť %(title)s",
|
"Edit %(title)s": "Upraviť %(title)s",
|
||||||
"Every result, even with nothing to report.": "Každý výsledok, aj keď nie je čo hlásiť.",
|
"Every result, even with nothing to report.": "Každý výsledok, aj keď nie je čo hlásiť.",
|
||||||
"Models for scheduled tasks": "Modely pre naplánované úlohy",
|
"Models for scheduled tasks": "Modely pre naplánované úlohy",
|
||||||
"No team": "Bez tímu",
|
|
||||||
"Only in its history.": "Len v jej histórii.",
|
"Only in its history.": "Len v jej histórii.",
|
||||||
"Open the chat": "Otvoriť konverzáciu",
|
"Open the chat": "Otvoriť konverzáciu",
|
||||||
"Result": "Výsledok",
|
"Result": "Výsledok",
|
||||||
|
|||||||
Generated
+1
-1
@@ -2085,7 +2085,7 @@ checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "llembas-desktop"
|
name = "llembas-desktop"
|
||||||
version = "1.0.0-beta.4"
|
version = "1.0.0-beta.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "llembas-desktop"
|
name = "llembas-desktop"
|
||||||
version = "1.0.0-beta.4"
|
version = "1.0.0-beta.5"
|
||||||
description = "LLeMbas Desktop: the agent on this computer and your LLeMbas server's chats, in an app of its own"
|
description = "LLeMbas Desktop: the agent on this computer and your LLeMbas server's chats, in an app of its own"
|
||||||
authors = ["Jaroslav Beneš"]
|
authors = ["Jaroslav Beneš"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"tag": "v2.0.0-beta.6",
|
"tag": "v2.0.0-beta.7",
|
||||||
"commit": "a71e9b4bdb8acb6e9949e978253a72c347cc67f3",
|
"commit": "e3bd54f4d210cff6adb4586199caed4f41987f5e",
|
||||||
"sha256": "e5f76f542ed5512702e06d69fa1dfa098c511f67dacbd7cc93f4a0618c6ae966"
|
"sha256": "c08adb703678408b469520ee41a0918424cb962ecc89d80f4cbdac5e5c437b92"
|
||||||
}
|
}
|
||||||
+119
-31
@@ -122,6 +122,7 @@ textarea { padding: var(--sp-2) var(--control-px-sm); resize: none; line-height:
|
|||||||
|
|
||||||
label, .label { font: var(--text-sm) / var(--leading-tight) var(--font-body); color: var(--ink-muted); }
|
label, .label { font: var(--text-sm) / var(--leading-tight) var(--font-body); color: var(--ink-muted); }
|
||||||
.hint { font-size: var(--text-sm); color: var(--ink-muted); min-height: calc(var(--text-sm) * var(--leading-normal)); }
|
.hint { font-size: var(--text-sm); color: var(--ink-muted); min-height: calc(var(--text-sm) * var(--leading-normal)); }
|
||||||
|
.hint.is-warn { color: var(--warning); }
|
||||||
.status { font: var(--text-sm) / var(--leading-normal) var(--font-body); }
|
.status { font: var(--text-sm) / var(--leading-normal) var(--font-body); }
|
||||||
.status.is-ok { color: var(--leaf); }
|
.status.is-ok { color: var(--leaf); }
|
||||||
.check { display: grid; grid-template-columns: var(--check-size) minmax(0, 1fr); gap: var(--sp-2); align-items: center; color: var(--ink); }
|
.check { display: grid; grid-template-columns: var(--check-size) minmax(0, 1fr); gap: var(--sp-2); align-items: center; color: var(--ink); }
|
||||||
@@ -708,26 +709,75 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
|||||||
}
|
}
|
||||||
.toggle input:checked { background: var(--accent); border-color: var(--accent); }
|
.toggle input:checked { background: var(--accent); border-color: var(--accent); }
|
||||||
.toggle input:checked::after { background: var(--accent-ink); transform: translateX(var(--check-size)); }
|
.toggle input:checked::after { background: var(--accent-ink); transform: translateX(var(--check-size)); }
|
||||||
/* The team's size: two number fields of one width with a dash between them, and in the header
|
/* The subagents, Min and Max: one range with two handles. Three rows of known height -- the
|
||||||
its word before them -- fixed tracks, so nothing moves when a number grows a digit. */
|
values said on one line, the track box (one control high), the two ends -- and in the box two
|
||||||
.agents {
|
range inputs laid over one track, the span between the handles filled. The inputs let only
|
||||||
display: inline-grid;
|
their handles take the pointer, so either can be grabbed wherever the other is. */
|
||||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
.range2 { display: grid; grid-template-rows: auto var(--control-h) auto; gap: var(--sp-1); width: 100%; }
|
||||||
gap: var(--sp-2);
|
.range2__said { font: 600 var(--text-sm) / var(--leading-tight) var(--font-ui); color: var(--ink); font-variant-numeric: tabular-nums; }
|
||||||
align-items: center;
|
.range2__box { position: relative; display: block; height: var(--control-h); --lo: 0%; --hi: 100%; }
|
||||||
|
.range2__track {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: calc(var(--check-size) / 2);
|
||||||
|
right: calc(var(--check-size) / 2);
|
||||||
|
height: var(--sp-1);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
}
|
||||||
|
.range2__fill {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: var(--lo);
|
||||||
|
right: calc(100% - var(--hi));
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
}
|
||||||
|
.range2__input {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: var(--control-h);
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
background: none;
|
||||||
|
pointer-events: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
}
|
}
|
||||||
.agents__dash { color: var(--ink-muted); }
|
.range2__input.is-top { z-index: 1; }
|
||||||
.agents__input { width: 100%; min-width: 0; height: var(--control-h); padding: 0 var(--control-px-sm); }
|
.range2__input::-webkit-slider-runnable-track { height: 100%; background: none; border: 0; }
|
||||||
.agents--compact {
|
.range2__input::-moz-range-track { height: 100%; background: none; border: 0; }
|
||||||
grid-template-columns: auto calc(var(--w-field-xs) / 2) auto calc(var(--w-field-xs) / 2);
|
.range2__input::-webkit-slider-thumb {
|
||||||
width: auto;
|
-webkit-appearance: none;
|
||||||
height: var(--control-h-sm);
|
appearance: none;
|
||||||
font: var(--text-sm) var(--font-ui);
|
width: var(--check-size);
|
||||||
|
height: var(--check-size);
|
||||||
|
margin-top: calc((var(--control-h) - var(--check-size)) / 2);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
border: var(--border-w-thick) solid var(--accent);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
cursor: grab;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
.agents--compact .agents__input { height: var(--control-h-sm); }
|
.range2__input::-moz-range-thumb {
|
||||||
.agents__label { color: var(--ink-muted); white-space: nowrap; }
|
width: var(--check-size);
|
||||||
|
height: var(--check-size);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
border: var(--border-w-thick) solid var(--accent);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
cursor: grab;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.range2__input:focus-visible { outline: none; }
|
||||||
|
.range2__input:focus-visible::-webkit-slider-thumb { box-shadow: var(--ring); }
|
||||||
|
.range2__input:focus-visible::-moz-range-thumb { box-shadow: var(--ring); }
|
||||||
|
/* The two ends, each one handle wide, so each number sits centred under its handle at that end. */
|
||||||
|
.range2__ends { display: flex; justify-content: space-between; font-size: var(--text-xs); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
|
||||||
|
.range2__ends > span { width: var(--check-size); text-align: center; white-space: nowrap; }
|
||||||
/* Markdown, drawn by markdown.js from model output (never as HTML). */
|
/* Markdown, drawn by markdown.js from model output (never as HTML). */
|
||||||
.md { overflow-wrap: anywhere; min-width: 0; }
|
.md { overflow-wrap: anywhere; min-width: 0; }
|
||||||
.md > * + * { margin-top: var(--sp-3); }
|
.md > * + * { margin-top: var(--sp-3); }
|
||||||
@@ -920,6 +970,14 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
|||||||
.ctx__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ink-muted); }
|
.ctx__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ink-muted); }
|
||||||
.ctx__num { font: var(--text-xs) var(--font-mono); text-align: right; color: var(--ink-muted); }
|
.ctx__num { font: var(--text-xs) var(--font-mono); text-align: right; color: var(--ink-muted); }
|
||||||
.ctx__note { margin: 0; font-size: var(--text-xs); color: var(--ink-faint); }
|
.ctx__note { margin: 0; font-size: var(--text-xs); color: var(--ink-faint); }
|
||||||
|
/* "41k until auto-compact" and the Compact button: the words give, the button keeps its size. */
|
||||||
|
.ctx__compact { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-1) var(--sp-2); align-items: center; padding-top: var(--sp-2); border-top: var(--border-w) solid var(--border); }
|
||||||
|
.ctx__until { color: var(--ink-muted); }
|
||||||
|
.ctx__error { grid-column: 1 / -1; margin: 0; font-size: var(--text-xs); color: var(--warning); }
|
||||||
|
/* Environment: an agent chat's folder and where it is. */
|
||||||
|
.env { display: grid; grid-template-columns: var(--sp-4) minmax(0, auto) auto; justify-content: start; gap: var(--sp-2); align-items: center; margin: 0; }
|
||||||
|
.env__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ink); }
|
||||||
|
.env__where { font: var(--text-xs) var(--font-mono); color: var(--ink-muted); }
|
||||||
.ctx--messages { --ctx: var(--accent); }
|
.ctx--messages { --ctx: var(--accent); }
|
||||||
.ctx--system { --ctx: var(--ink-muted); }
|
.ctx--system { --ctx: var(--ink-muted); }
|
||||||
.ctx--memory { --ctx: var(--warning); }
|
.ctx--memory { --ctx: var(--warning); }
|
||||||
@@ -943,15 +1001,41 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
|||||||
track that gives, cut), elapsed time in a column of its own width, the stop button at its size
|
track that gives, cut), elapsed time in a column of its own width, the stop button at its size
|
||||||
(an empty box where there is none) -- so rows line up whatever each says. */
|
(an empty box where there is none) -- so rows line up whatever each says. */
|
||||||
.tasks { display: grid; gap: var(--sp-2); min-width: 0; }
|
.tasks { display: grid; gap: var(--sp-2); min-width: 0; }
|
||||||
.tasks__summary, .tasks__team { margin: 0; color: var(--ink-muted); }
|
.tasks__team { margin: 0; color: var(--ink-muted); }
|
||||||
.tasks__goal { margin: 0; color: var(--ink); }
|
.tasks__goal { margin: 0; color: var(--ink); }
|
||||||
.tasks__list { display: grid; gap: var(--sp-1); }
|
.tasks__group { display: grid; grid-template-columns: auto auto; justify-content: start; gap: var(--sp-2); align-items: baseline; margin: var(--sp-1) 0 0; color: var(--ink-muted); }
|
||||||
.tasks__more > summary { cursor: pointer; padding: var(--sp-1) 0; color: var(--ink-muted); }
|
.tasks__list { display: grid; gap: var(--sp-2); }
|
||||||
.tasks__more[open] > summary { margin-bottom: var(--sp-1); }
|
/* A fold ("Show 3 more", "Finished 12", a task's own "2 tasks"): a chevron that turns, then the
|
||||||
|
words; nested ones step in by the depth of what is under them. */
|
||||||
|
.tasks__more { margin-left: calc(var(--depth, 0) * var(--sp-4)); min-width: 0; }
|
||||||
|
.tasks__more > summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 12px minmax(0, 1fr);
|
||||||
|
gap: var(--sp-2);
|
||||||
|
align-items: center;
|
||||||
|
min-height: var(--row-h);
|
||||||
|
padding: 0 var(--sp-3);
|
||||||
|
color: var(--ink-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.tasks__more > summary::-webkit-details-marker { display: none; }
|
||||||
|
.tasks__more > summary:hover { color: var(--ink); }
|
||||||
|
.tasks__more[open] > summary { margin-bottom: var(--sp-2); }
|
||||||
|
.tasks__nested { margin-top: calc(-1 * var(--sp-1)); }
|
||||||
|
.tasks__finished { padding-top: var(--sp-1); border-top: var(--border-w) solid var(--border); }
|
||||||
|
.tasks__chev, .agent__chev { display: grid; place-items: center; width: 12px; height: 12px; color: var(--ink-faint); transition: transform var(--dur-1) var(--ease-out); }
|
||||||
|
.tasks__chev .icon, .agent__chev .icon { width: 12px; height: 12px; }
|
||||||
|
.tasks__more[open] > summary .tasks__chev, .agent[open] > .agent__row .agent__chev { transform: rotate(90deg); }
|
||||||
|
@media (prefers-reduced-motion: reduce) { .tasks__chev, .agent__chev { transition: none; } }
|
||||||
|
|
||||||
|
/* A task: one card, one row of known tracks -- the chevron, what it does (the one track that
|
||||||
|
gives, cut), the elapsed time in a column of its own width, the stop square at its size (an
|
||||||
|
empty box where it cannot be stopped) -- so the rows line up whatever each says. */
|
||||||
.agent { margin-left: calc(var(--depth, 0) * var(--sp-4)); background: var(--surface); border: var(--border-w) solid var(--border); border-radius: var(--radius-lg); min-width: 0; }
|
.agent { margin-left: calc(var(--depth, 0) * var(--sp-4)); background: var(--surface); border: var(--border-w) solid var(--border); border-radius: var(--radius-lg); min-width: 0; }
|
||||||
.agent__row {
|
.agent__row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: var(--dot-size) minmax(0, 1fr) var(--time-col) var(--control-h-sm);
|
grid-template-columns: 12px minmax(0, 1fr) var(--time-col) var(--control-h-sm);
|
||||||
gap: var(--sp-2);
|
gap: var(--sp-2);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: var(--control-h);
|
min-height: var(--control-h);
|
||||||
@@ -960,17 +1044,21 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
.agent__row::-webkit-details-marker { display: none; }
|
.agent__row::-webkit-details-marker { display: none; }
|
||||||
.agent__dot { width: var(--dot-size); height: var(--dot-size); border-radius: var(--radius-full); background: var(--ink-faint); }
|
.agent__row:hover { background: var(--surface-hover); border-radius: var(--radius-lg); }
|
||||||
.agent.is-working .agent__dot { background: var(--accent); }
|
|
||||||
.agent.is-queued .agent__dot, .agent.is-waiting .agent__dot { background: var(--warning); }
|
|
||||||
.agent.is-done .agent__dot { background: var(--leaf); }
|
|
||||||
.agent.is-error .agent__dot { background: var(--danger); }
|
|
||||||
.agent__what { display: grid; min-width: 0; }
|
.agent__what { display: grid; min-width: 0; }
|
||||||
.agent__name, .agent__role { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
.agent__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ink); }
|
||||||
.agent__name { color: var(--ink); }
|
.agent.is-done .agent__name, .agent.is-stopped .agent__name { color: var(--ink-muted); }
|
||||||
.agent__role { font-size: var(--text-xs); color: var(--ink-muted); }
|
.agent.is-error .agent__name { color: var(--danger); }
|
||||||
|
.agent__role { margin: 0; color: var(--ink-muted); overflow-wrap: anywhere; }
|
||||||
.agent__time { font: var(--text-xs) var(--font-mono); text-align: right; color: var(--ink-muted); }
|
.agent__time { font: var(--text-xs) var(--font-mono); text-align: right; color: var(--ink-muted); }
|
||||||
.agent__stop { width: var(--control-h-sm); height: var(--control-h-sm); }
|
.agent.is-queued .agent__time, .agent.is-waiting .agent__time { color: var(--warning); }
|
||||||
|
/* The stop square: a small filled square in a button the size of the others. */
|
||||||
|
.agent__stop { display: grid; place-items: center; width: var(--control-h-sm); height: var(--control-h-sm); padding: 0; background: var(--surface-active); border: 0; border-radius: var(--radius-md); cursor: pointer; }
|
||||||
|
.agent__stop:hover { background: var(--surface-hover); }
|
||||||
|
.agent__stop.is-none { background: none; cursor: default; }
|
||||||
|
.agent__square { width: var(--sp-3); height: var(--sp-3); background: var(--ink); border-radius: var(--radius-sm); }
|
||||||
|
.agent__stop:hover .agent__square { background: var(--danger); }
|
||||||
|
.agent__id { font: var(--text-xs) var(--font-mono); color: var(--ink-faint); overflow-wrap: anywhere; }
|
||||||
.agent__body { display: grid; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3) var(--sp-3); border-top: var(--border-w) solid var(--border); min-width: 0; }
|
.agent__body { display: grid; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3) var(--sp-3); border-top: var(--border-w) solid var(--border); min-width: 0; }
|
||||||
.agent__body > .btn { justify-self: start; }
|
.agent__body > .btn { justify-self: start; }
|
||||||
.agent__facts { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); margin: 0; font-size: var(--text-xs); color: var(--ink-muted); }
|
.agent__facts { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); margin: 0; font-size: var(--text-xs); color: var(--ink-muted); }
|
||||||
|
|||||||
+208
-88
File diff suppressed because it is too large.
Load diff
+207
-60
@@ -1,14 +1,18 @@
|
|||||||
// The chat's activity panel, beside it: what is in its context, by category, against the model's
|
// The chat's activity panel, beside it, opened from the header's button at any time: where an
|
||||||
// window; the files the chat and its agents wrote; every background agent -- a team's members as
|
// agent chat works; what is in its context, by category, against the model's window, how far it
|
||||||
// a tree, helpers and commands -- with its state, how long it has run, its model, its latest step
|
// is to the automatic compaction and a button to compact now; the files the chat and its
|
||||||
// and what it is writing, its whole conversation read-only and a button to stop it; the team's
|
// subagents wrote; every background task -- subagents, a team's members as a tree under the one
|
||||||
// room; and the plan. The strip above the message box sums the agents up and opens it. The same
|
// that leads them, background commands -- with how long it has run and a stop square, unfolding
|
||||||
// facts as the server's own panel (`services/activity.py`), from /api/v2:
|
// to its latest step, what it is writing and its whole conversation, read-only; the finished ones
|
||||||
|
// folded; the team's room; and the plan. The line above the message box sums up what runs, while
|
||||||
|
// anything does, and opens it. The same facts as the server's own panel (`services/activity.py`),
|
||||||
|
// from /api/v2:
|
||||||
//
|
//
|
||||||
// GET /chats/{chat}/activity?since=&context= everything, with a `version`
|
// GET /chats/{chat}/activity?since=&context= everything, with a `version`
|
||||||
// GET /chats/{chat}/activity/events `activity` {version, summary} on each change
|
// GET /chats/{chat}/activity/events `activity` {version, summary} on each change
|
||||||
// GET /chats/{chat}/agents/{kind}/{id} one agent and its conversation
|
// GET /chats/{chat}/agents/{kind}/{id} one task and its conversation
|
||||||
// POST /chats/{chat}/agents/{kind}/{id}/stop
|
// POST /chats/{chat}/agents/{kind}/{id}/stop
|
||||||
|
// POST /chats/{chat}/compact summarise the earlier turns now
|
||||||
//
|
//
|
||||||
// The events stream is followed for as long as a chat is open: it keeps the strip current, and
|
// The events stream is followed for as long as a chat is open: it keeps the strip current, and
|
||||||
// while the panel is open a new `version` reads the panel again. The context is the costly part
|
// while the panel is open a new `version` reads the panel again. The context is the costly part
|
||||||
@@ -31,6 +35,8 @@ const SHOWN = 6
|
|||||||
const CONTEXT_EVERY = 15000
|
const CONTEXT_EVERY = 15000
|
||||||
|
|
||||||
let layout = () => {} // (on) -> the shell makes room for the panel
|
let layout = () => {} // (on) -> the shell makes room for the panel
|
||||||
|
let onThisComputer = () => false // (chat) -> whether an agent chat works on this computer
|
||||||
|
let onCompacted = () => {} // (chat id) -> the conversation is read again
|
||||||
let chat = null // the open chat: { id, kind, … }
|
let chat = null // the open chat: { id, kind, … }
|
||||||
let stream = null // the events stream's handle
|
let stream = null // the events stream's handle
|
||||||
let retry = 0 // the reconnect timer
|
let retry = 0 // the reconnect timer
|
||||||
@@ -69,12 +75,14 @@ export function compact(value) {
|
|||||||
return `${trim(n / 1000000, 2)}M`
|
return `${trim(n / 1000000, 2)}M`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** "3 agents working · 1 queued · 2 finished", or "" for none (the server's `summary_text`). */
|
/**
|
||||||
|
* "3 running · 1 queued · 2 finished", or "" for none: every background task -- a subagent, a
|
||||||
|
* team's member, a command -- counts the same, as the panel lists them together.
|
||||||
|
*/
|
||||||
export function summaryText(counts) {
|
export function summaryText(counts) {
|
||||||
if (!counts) return ""
|
if (!counts) return ""
|
||||||
const parts = []
|
const parts = []
|
||||||
if (counts.working === 1) parts.push(t("1 agent working"))
|
if (counts.working) parts.push(t("%(n)s running", { n: counts.working }))
|
||||||
else if (counts.working) parts.push(t("%(n)s agents working", { n: counts.working }))
|
|
||||||
if (counts.queued) parts.push(t("%(n)s queued", { n: counts.queued }))
|
if (counts.queued) parts.push(t("%(n)s queued", { n: counts.queued }))
|
||||||
if (counts.waiting) parts.push(t("%(n)s waiting", { n: counts.waiting }))
|
if (counts.waiting) parts.push(t("%(n)s waiting", { n: counts.waiting }))
|
||||||
if (counts.finished) parts.push(t("%(n)s finished", { n: counts.finished }))
|
if (counts.finished) parts.push(t("%(n)s finished", { n: counts.finished }))
|
||||||
@@ -141,9 +149,10 @@ export function watchActivity(next) {
|
|||||||
cursor = 0
|
cursor = 0
|
||||||
readAt = contextAt = 0
|
readAt = contextAt = 0
|
||||||
view = null
|
view = null
|
||||||
|
compactError = ""
|
||||||
openRows.clear()
|
openRows.clear()
|
||||||
renderStrip()
|
renderStrip()
|
||||||
$("activity-open")?.setAttribute("aria-expanded", String(panelOpen()))
|
markOpen()
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
if (panelOpen()) showPanel(false)
|
if (panelOpen()) showPanel(false)
|
||||||
return
|
return
|
||||||
@@ -269,36 +278,58 @@ async function read(withContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- The strip above the message box ---------------------------------------------------------------
|
// --- The line above the message box, and the header's button ---------------------------------------
|
||||||
|
|
||||||
|
/** Both ways into the panel say whether it is open. */
|
||||||
|
function markOpen() {
|
||||||
|
for (const id of ["activity-open", "activity-strip-btn"]) $(id)?.setAttribute("aria-expanded", String(panelOpen()))
|
||||||
|
}
|
||||||
|
|
||||||
function renderStrip() {
|
function renderStrip() {
|
||||||
const strip = $("activity-strip")
|
const strip = $("activity-strip")
|
||||||
// A chip for every open chat: what runs, while something does, as the server's strip says it (a
|
// Only while something runs, as the server's own line: the header's button is the way into the
|
||||||
// chat whose helpers all finished long ago does not carry "12 finished" for ever: the panel
|
// panel at any other time (and a chat whose tasks all finished long ago does not carry
|
||||||
// still lists them); else only the way into the panel.
|
// "12 finished" for ever: the panel still lists them).
|
||||||
strip.hidden = !chat
|
const active = chat && summary?.active ? summaryText(summary) : ""
|
||||||
if (!chat) return clear(strip)
|
strip.hidden = !active
|
||||||
const active = summary?.active ? summaryText(summary) : ""
|
if (!active) return clear(strip)
|
||||||
const text = active || t("Activity")
|
|
||||||
strip.replaceChildren(
|
strip.replaceChildren(
|
||||||
h(
|
h(
|
||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
id: "activity-open",
|
id: "activity-strip-btn",
|
||||||
class: `strip__btn${active ? " is-active" : ""}`,
|
class: "strip__btn is-active",
|
||||||
"aria-label": `${text} — ${panelOpen() ? t("Close the activity panel") : t("Open the activity panel")}`,
|
"aria-label": `${active} — ${panelOpen() ? t("Close the activity panel") : t("Open the activity panel")}`,
|
||||||
"aria-controls": "activity",
|
"aria-controls": "activity",
|
||||||
"aria-expanded": String(panelOpen()),
|
"aria-expanded": String(panelOpen()),
|
||||||
onclick: togglePanel,
|
onclick: togglePanel,
|
||||||
},
|
},
|
||||||
h("span", { class: "dot", "aria-hidden": "true" }),
|
h("span", { class: "dot", "aria-hidden": "true" }),
|
||||||
h("span", { class: "strip__text", text }),
|
h("span", { class: "strip__text", text: active }),
|
||||||
icon(ICONS.chevron),
|
icon(ICONS.chevron),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The header's button: the panel, open or closed, for every open chat. */
|
||||||
|
export function activityButton() {
|
||||||
|
return h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
class: "tool",
|
||||||
|
id: "activity-open",
|
||||||
|
"aria-label": t("Context and background tasks"),
|
||||||
|
title: t("Context and background tasks"),
|
||||||
|
"aria-controls": "activity",
|
||||||
|
"aria-expanded": String(panelOpen()),
|
||||||
|
onclick: togglePanel,
|
||||||
|
},
|
||||||
|
icon(ICONS.tasks),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// --- The panel ------------------------------------------------------------------------------------------
|
// --- The panel ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/** Open or close the panel (the header's Activity button, the strip, Escape). */
|
/** Open or close the panel (the header's Activity button, the strip, Escape). */
|
||||||
@@ -306,14 +337,16 @@ export function showPanel(on) {
|
|||||||
if (on && !chat) return
|
if (on && !chat) return
|
||||||
$("activity").hidden = !on
|
$("activity").hidden = !on
|
||||||
layout(on)
|
layout(on)
|
||||||
$("activity-open")?.setAttribute("aria-expanded", String(on))
|
markOpen()
|
||||||
clearInterval(ticker)
|
clearInterval(ticker)
|
||||||
ticker = 0
|
ticker = 0
|
||||||
if (!on) {
|
if (!on) {
|
||||||
view = null
|
view = null
|
||||||
|
renderStrip()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ticker = setInterval(tick, 1000)
|
ticker = setInterval(tick, 1000)
|
||||||
|
renderStrip()
|
||||||
render()
|
render()
|
||||||
read(true)
|
read(true)
|
||||||
$("activity-close").focus()
|
$("activity-close").focus()
|
||||||
@@ -359,15 +392,43 @@ function render() {
|
|||||||
}
|
}
|
||||||
// Kept where the reader was: the panel is drawn again on every change.
|
// Kept where the reader was: the panel is drawn again on every change.
|
||||||
const scroll = body.scrollTop
|
const scroll = body.scrollTop
|
||||||
body.replaceChildren(
|
const entries = data.agents || []
|
||||||
|
const parts = [
|
||||||
|
chat?.kind === "agent" && chat.device ? section("environment", t("Environment"), 0, environmentPart(chat)) : null,
|
||||||
section("context", t("Context"), 0, contextPart(data.context)),
|
section("context", t("Context"), 0, contextPart(data.context)),
|
||||||
section("outputs", t("Outputs"), (data.outputs || []).length, outputsPart(data.outputs || [])),
|
section("outputs", t("Outputs"), (data.outputs || []).length, outputsPart(data.outputs || [])),
|
||||||
section("tasks", t("Background tasks"), 0, tasksPart()),
|
section("tasks", t("Background tasks"), 0, tasksPart(entries)),
|
||||||
|
data.team ? section("room", t("Room"), room.length, roomPart()) : null,
|
||||||
data.plan ? section("plan", t("Plan"), 0, h("div", { class: "activity__plan" }, planList(data.plan))) : null,
|
data.plan ? section("plan", t("Plan"), 0, h("div", { class: "activity__plan" }, planList(data.plan))) : null,
|
||||||
)
|
]
|
||||||
|
body.replaceChildren(...parts.filter(Boolean))
|
||||||
body.scrollTop = scroll
|
body.scrollTop = scroll
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** How many tasks, nested ones included: those still at work, or those finished. */
|
||||||
|
function count(list, active) {
|
||||||
|
let n = 0
|
||||||
|
for (const a of list) {
|
||||||
|
if (ACTIVE.includes(a.state) === active) n += 1
|
||||||
|
n += count(a.children || [], active)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Where an agent chat works: its folder, on this computer or another device. */
|
||||||
|
function environmentPart(c) {
|
||||||
|
const dir = c.device?.directory || ""
|
||||||
|
const name = dir.split(/[\\/]/).filter(Boolean).pop() || dir
|
||||||
|
const here = onThisComputer(c)
|
||||||
|
return h(
|
||||||
|
"p",
|
||||||
|
{ class: "env", title: dir },
|
||||||
|
icon(ICONS.folder),
|
||||||
|
h("bdi", { class: "env__name", text: name }),
|
||||||
|
h("span", { class: "env__where", text: here ? t("This computer") : t("Another device") }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function contextPart(c) {
|
function contextPart(c) {
|
||||||
if (!c) return h("p", { class: "activity__empty", text: t("Not measured.") })
|
if (!c) return h("p", { class: "activity__empty", text: t("Not measured.") })
|
||||||
const known = c.percent !== null && c.percent !== undefined
|
const known = c.percent !== null && c.percent !== undefined
|
||||||
@@ -410,7 +471,67 @@ function contextPart(c) {
|
|||||||
{ class: "ctx__note" },
|
{ class: "ctx__note" },
|
||||||
`${c.measured ? t("Counted by the model at the last reply; the categories are estimated.") : t("Estimated: no reply has been counted yet.")} ${t("%(n)s tools offered.", { n: c.tools_count ?? 0 })}`,
|
`${c.measured ? t("Counted by the model at the last reply; the categories are estimated.") : t("Estimated: no reply has been counted yet.")} ${t("%(n)s tools offered.", { n: c.tools_count ?? 0 })}`,
|
||||||
)
|
)
|
||||||
return h("div", { class: "ctx" }, used, bar, rows, note)
|
return h("div", { class: "ctx" }, used, bar, rows, compactRow(c), note)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How far it is to the automatic compaction, and the button that compacts now. The server says it
|
||||||
|
* (2.0.0-beta.7: `auto_compact`, `until_compact` in tokens, `compactable` whether Compact can run
|
||||||
|
* now); an older one gives only `threshold` -- the server compacts before a turn once the context
|
||||||
|
* would reach that % of the window, 0 is off -- and it is worked out from that.
|
||||||
|
*/
|
||||||
|
function compactRow(c) {
|
||||||
|
const limit = Number(c.limit) || 0
|
||||||
|
const threshold = Number(c.threshold)
|
||||||
|
const auto = "auto_compact" in c ? !!c.auto_compact : threshold > 0
|
||||||
|
let until = c.until_compact
|
||||||
|
if (until === undefined && auto && limit && threshold < 100) until = Math.floor((limit * threshold) / 100) - (Number(c.used) || 0)
|
||||||
|
let words
|
||||||
|
if (!auto) words = t("Automatic compaction is off.")
|
||||||
|
else if (until === null || until === undefined) words = t("Compacts by itself once the window is known.")
|
||||||
|
else words = until > 0 ? t("%(tokens)s until auto-compact", { tokens: compact(until) }) : t("Compacts before the next reply.")
|
||||||
|
const busy = compacting === chat?.id
|
||||||
|
// `compactable` false: nothing to summarise yet, a reply being written, compaction turned off --
|
||||||
|
// the button stays, greyed; pressing an older server's says why in its own words.
|
||||||
|
const can = !("compactable" in c) || !!c.compactable
|
||||||
|
return h(
|
||||||
|
"div",
|
||||||
|
{ class: "ctx__compact" },
|
||||||
|
h("span", { class: "ctx__until", text: words }),
|
||||||
|
h("button", {
|
||||||
|
type: "button",
|
||||||
|
class: "btn btn--sm",
|
||||||
|
text: busy ? t("Compacting…") : t("Compact"),
|
||||||
|
disabled: busy || !can,
|
||||||
|
title: t("Summarise the earlier turns now and send the summary in their place."),
|
||||||
|
onclick: compactNow,
|
||||||
|
}),
|
||||||
|
compactError ? h("p", { class: "ctx__error", role: "alert", text: compactError }) : null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let compacting = null // the chat being compacted
|
||||||
|
let compactError = ""
|
||||||
|
|
||||||
|
async function compactNow() {
|
||||||
|
if (!chat || compacting) return
|
||||||
|
const id = chat.id
|
||||||
|
compacting = id
|
||||||
|
compactError = ""
|
||||||
|
render()
|
||||||
|
try {
|
||||||
|
await post(`/chats/${enc(id)}/compact`)
|
||||||
|
compacting = null
|
||||||
|
if (chat?.id !== id) return
|
||||||
|
onCompacted(id)
|
||||||
|
read(true)
|
||||||
|
} catch (err) {
|
||||||
|
compacting = null
|
||||||
|
if (chat?.id !== id) return
|
||||||
|
// A server without the route in its /api/v2 says 404 or 405: said as what it means.
|
||||||
|
compactError = err?.status === 404 || err?.status === 405 ? t("This server cannot compact a chat from the app yet; the web interface can.") : why(err)
|
||||||
|
render()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A category's key as a class name: the server's words, kept to letters and underscores. */
|
/** A category's key as a class name: the server's words, kept to letters and underscores. */
|
||||||
@@ -439,31 +560,33 @@ function outputsPart(outputs) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function tasksPart() {
|
function tasksPart(entries) {
|
||||||
const entries = data.agents || []
|
|
||||||
const team = data.team
|
const team = data.team
|
||||||
const box = h("div", { class: "tasks", "aria-live": "polite" })
|
const box = h("div", { class: "tasks", "aria-live": "polite" })
|
||||||
if (!entries.length && !team) {
|
if (!entries.length && !team) {
|
||||||
box.append(h("p", { class: "activity__empty", text: t("Nothing runs in the background. Helpers, commands and a team's members show here while they work.") }))
|
box.append(h("p", { class: "activity__empty", text: t("Nothing runs in the background. Subagents, a team's members and background commands show here while they work.") }))
|
||||||
return box
|
return box
|
||||||
}
|
}
|
||||||
box.append(h("p", { class: "tasks__summary", text: summaryText(data.summary) || t("Nothing running.") }))
|
|
||||||
if (team) {
|
if (team) {
|
||||||
box.append(h("p", { class: "tasks__team", text: t("Team: %(size)s of %(max)s agents", { size: team.size, max: team.max }) }))
|
box.append(h("p", { class: "tasks__team", text: t("Team: %(size)s of at most %(max)s subagents", { size: team.size, max: team.max }) }))
|
||||||
if (team.goal) box.append(h("p", { class: "tasks__goal", text: team.goal }))
|
if (team.goal) box.append(h("p", { class: "tasks__goal", text: team.goal }))
|
||||||
}
|
}
|
||||||
const live = entries.filter((e) => ACTIVE.includes(e.state))
|
// At the top level, what still runs -- with its own tasks under it, finished or not -- and then
|
||||||
const ended = entries.filter((e) => !ACTIVE.includes(e.state))
|
// what finished with nothing under it still at work, folded.
|
||||||
|
const live = entries.filter((e) => ACTIVE.includes(e.state) || count(e.children || [], true))
|
||||||
|
const ended = entries.filter((e) => !live.includes(e))
|
||||||
|
box.append(h("p", { class: "tasks__group" }, h("span", { text: t("Running") }), h("span", { class: "activity__count", text: String(count(entries, true)) })))
|
||||||
if (live.length) {
|
if (live.length) {
|
||||||
box.append(h("div", { class: "tasks__list" }, live.slice(0, SHOWN).map(agentRows)))
|
box.append(h("div", { class: "tasks__list" }, live.slice(0, SHOWN).map(agentRows)))
|
||||||
if (live.length > SHOWN) box.append(fold("more", t("Show %(n)s more", { n: live.length - SHOWN }), live.slice(SHOWN)))
|
if (live.length > SHOWN) box.append(fold("more", t("Show %(n)s more", { n: live.length - SHOWN }), live.slice(SHOWN)))
|
||||||
}
|
} else box.append(h("p", { class: "activity__empty", text: t("Nothing running.") }))
|
||||||
if (ended.length) box.append(fold("finished", t("%(n)s finished", { n: ended.length }), ended))
|
if (ended.length) box.append(fold("finished", t("Finished %(n)s", { n: count(ended, false) }), ended, "tasks__finished"))
|
||||||
if (team) {
|
return box
|
||||||
box.append(h("h3", { class: "activity__subheading", text: t("Room") }))
|
}
|
||||||
box.append(
|
|
||||||
room.length
|
function roomPart() {
|
||||||
? h(
|
if (!room.length) return h("p", { class: "activity__empty", text: t("Nothing has been said in the room yet.") })
|
||||||
|
return h(
|
||||||
"ol",
|
"ol",
|
||||||
{ class: "room" },
|
{ class: "room" },
|
||||||
room.map((m) =>
|
room.map((m) =>
|
||||||
@@ -481,22 +604,27 @@ function tasksPart() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: h("p", { class: "activity__empty", text: t("Nothing has been said in the room yet.") }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return box
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A folded list of agents ("Show 3 more", "12 finished"), remembered open across redraws. */
|
/** A folded list of tasks ("Show 3 more", "Finished 12", "Tasks: 2"), remembered across redraws. */
|
||||||
function fold(name, label, list) {
|
function fold(name, label, list, cls = "") {
|
||||||
const el = h("details", { class: "tasks__more", open: openRows.has(`fold:${name}`) }, h("summary", { text: label }), h("div", { class: "tasks__list" }, list.map(agentRows)))
|
const el = h(
|
||||||
|
"details",
|
||||||
|
{ class: `tasks__more ${cls}`, open: openRows.has(`fold:${name}`) },
|
||||||
|
h("summary", null, h("span", { class: "tasks__chev", "aria-hidden": "true" }, icon(ICONS.chevron)), h("span", { text: label })),
|
||||||
|
h("div", { class: "tasks__list" }, list.map(agentRows)),
|
||||||
|
)
|
||||||
el.addEventListener("toggle", () => (el.open ? openRows.add(`fold:${name}`) : openRows.delete(`fold:${name}`)))
|
el.addEventListener("toggle", () => (el.open ? openRows.add(`fold:${name}`) : openRows.delete(`fold:${name}`)))
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An agent and, under it, the agents it leads or started. */
|
/** A task and, folded under it, the tasks it leads or started ("Tasks: 3"). */
|
||||||
function agentRows(a) {
|
function agentRows(a) {
|
||||||
return [agentRow(a), ...(a.children || []).map(agentRows)]
|
const kids = a.children || []
|
||||||
|
if (!kids.length) return agentRow(a)
|
||||||
|
const nested = fold(`under:${a.kind}:${a.id}`, t("Tasks: %(n)s", { n: kids.length }), kids, "tasks__nested")
|
||||||
|
nested.style.setProperty("--depth", String(Math.max(0, Math.min(6, (Number(a.depth) || 0) + 1))))
|
||||||
|
return [agentRow(a), nested]
|
||||||
}
|
}
|
||||||
|
|
||||||
function agentRow(a) {
|
function agentRow(a) {
|
||||||
@@ -512,7 +640,7 @@ function agentRow(a) {
|
|||||||
"button",
|
"button",
|
||||||
{
|
{
|
||||||
type: "button",
|
type: "button",
|
||||||
class: "pane__btn agent__stop",
|
class: "agent__stop",
|
||||||
"aria-label": t("Stop %(name)s", { name: a.name }),
|
"aria-label": t("Stop %(name)s", { name: a.name }),
|
||||||
title: t("Stop %(name)s", { name: a.name }),
|
title: t("Stop %(name)s", { name: a.name }),
|
||||||
onclick: (e) => {
|
onclick: (e) => {
|
||||||
@@ -521,13 +649,15 @@ function agentRow(a) {
|
|||||||
stopAgent(a)
|
stopAgent(a)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
icon(ICONS.stop),
|
h("span", { class: "agent__square", "aria-hidden": "true" }),
|
||||||
)
|
)
|
||||||
: h("span", { class: "agent__stop", "aria-hidden": "true" })
|
: h("span", { class: "agent__stop is-none", "aria-hidden": "true" })
|
||||||
const facts = h(
|
const facts = h(
|
||||||
"p",
|
"p",
|
||||||
{ class: "agent__facts" },
|
{ class: "agent__facts" },
|
||||||
h("span", { class: "agent__state", text: stateWord(a.state) }),
|
h("span", { class: "agent__state", text: stateWord(a.state) }),
|
||||||
|
h("span", { text: kindWord(a) }),
|
||||||
|
a.kind !== "member" && a.role ? h("code", { class: "agent__id", text: a.name || a.id }) : null,
|
||||||
a.agent ? h("span", { text: a.agent }) : null,
|
a.agent ? h("span", { text: a.agent }) : null,
|
||||||
a.model ? h("span", { class: "agent__model", text: a.model }) : null,
|
a.model ? h("span", { class: "agent__model", text: a.model }) : null,
|
||||||
a.steps ? h("span", { text: t("%(steps)s steps, %(tools)s tools", { steps: a.steps, tools: a.tools }) }) : null,
|
a.steps ? h("span", { text: t("%(steps)s steps, %(tools)s tools", { steps: a.steps, tools: a.tools }) }) : null,
|
||||||
@@ -547,8 +677,8 @@ function agentRow(a) {
|
|||||||
h(
|
h(
|
||||||
"summary",
|
"summary",
|
||||||
{ class: "agent__row" },
|
{ class: "agent__row" },
|
||||||
h("span", { class: "agent__dot", "aria-hidden": "true" }),
|
h("span", { class: "agent__chev", "aria-hidden": "true" }, icon(ICONS.chevron)),
|
||||||
h("span", { class: "agent__what" }, h("span", { class: "agent__name", text: a.name || a.id }), a.role ? h("span", { class: "agent__role", text: a.role }) : null),
|
h("span", { class: "agent__what", title: label(a) }, h("bdi", { class: "agent__name", text: label(a) })),
|
||||||
time,
|
time,
|
||||||
stopBtn,
|
stopBtn,
|
||||||
),
|
),
|
||||||
@@ -556,12 +686,11 @@ function agentRow(a) {
|
|||||||
"div",
|
"div",
|
||||||
{ class: "agent__body" },
|
{ class: "agent__body" },
|
||||||
facts,
|
facts,
|
||||||
|
a.kind === "member" && a.role ? h("p", { class: "agent__role", text: a.role }) : null,
|
||||||
a.brief ? h("p", { class: "agent__brief", text: a.brief }) : null,
|
a.brief ? h("p", { class: "agent__brief", text: a.brief }) : null,
|
||||||
step,
|
step,
|
||||||
a.text ? h("p", { class: "agent__text", text: a.text }) : null,
|
a.text ? h("p", { class: "agent__text", text: a.text }) : null,
|
||||||
a.transcript
|
a.transcript ? h("button", { type: "button", class: "btn btn--sm", text: t("Open its conversation"), onclick: () => openAgent(a) }) : null,
|
||||||
? h("button", { type: "button", class: "btn btn--sm", text: t("Open its conversation"), onclick: () => openAgent(a) })
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
el.style.setProperty("--depth", String(Math.max(0, Math.min(6, Number(a.depth) || 0))))
|
el.style.setProperty("--depth", String(Math.max(0, Math.min(6, Number(a.depth) || 0))))
|
||||||
@@ -569,6 +698,22 @@ function agentRow(a) {
|
|||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task's line: a team's member by its name (it is how the room and the others call it); a
|
||||||
|
* subagent or a command by what it was given to do, its id only where there is nothing else.
|
||||||
|
*/
|
||||||
|
function label(a) {
|
||||||
|
if (a.kind === "member") return a.name || a.id
|
||||||
|
return a.role || a.brief || a.name || a.id
|
||||||
|
}
|
||||||
|
|
||||||
|
/** What a task is: a team's member, a subagent, or a background command. */
|
||||||
|
function kindWord(a) {
|
||||||
|
if (a.kind === "member") return t("Team member")
|
||||||
|
if (a.type === "command") return t("Background command")
|
||||||
|
return t("Subagent")
|
||||||
|
}
|
||||||
|
|
||||||
async function stopAgent(a) {
|
async function stopAgent(a) {
|
||||||
if (!chat) return
|
if (!chat) return
|
||||||
if (a.kind === "member") {
|
if (a.kind === "member") {
|
||||||
@@ -667,8 +812,10 @@ async function readAgent() {
|
|||||||
|
|
||||||
// --- Start ------------------------------------------------------------------------------------------
|
// --- Start ------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
export function initActivity({ onLayout }) {
|
export function initActivity({ onLayout, here, compacted }) {
|
||||||
layout = onLayout || layout
|
layout = onLayout || layout
|
||||||
|
onThisComputer = here || onThisComputer
|
||||||
|
onCompacted = compacted || onCompacted
|
||||||
$("activity-close").replaceChildren(icon(ICONS.close))
|
$("activity-close").replaceChildren(icon(ICONS.close))
|
||||||
$("activity-close").addEventListener("click", () => showPanel(false))
|
$("activity-close").addEventListener("click", () => showPanel(false))
|
||||||
$("activity-back").replaceChildren(icon(ICONS.back))
|
$("activity-back").replaceChildren(icon(ICONS.back))
|
||||||
|
|||||||
+35
-18
@@ -13,9 +13,9 @@ import { markdown } from "./markdown.js"
|
|||||||
import { openViewer, showKind } from "./viewer.js"
|
import { openViewer, showKind } from "./viewer.js"
|
||||||
import { readLocal } from "./explorer.js"
|
import { readLocal } from "./explorer.js"
|
||||||
import { menu, ask, showLine, popover, closePopover } from "./popup.js"
|
import { menu, ask, showLine, popover, closePopover } from "./popup.js"
|
||||||
import { avatar, agentsControl, teamPossible, levelsOf, levelWord, picture } from "./models.js"
|
import { avatar, agentsControl, teamPossible, namedOnly, subagentsCap, SUBAGENTS_HINT, SUBAGENTS_OWN, levelsOf, levelWord, picture } from "./models.js"
|
||||||
import { readyIds, sending, sent, discard } from "./attach.js"
|
import { readyIds, sending, sent, discard } from "./attach.js"
|
||||||
import { watchActivity, activityChanged, planList } from "./activity.js"
|
import { watchActivity, activityChanged, planList, activityButton } from "./activity.js"
|
||||||
import { agentBlock, agentReady, agentState, onAgentChange } from "./agent.js"
|
import { agentBlock, agentReady, agentState, onAgentChange } from "./agent.js"
|
||||||
import { folderName } from "./list.js"
|
import { folderName } from "./list.js"
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export function openChatId() {
|
|||||||
|
|
||||||
// --- Reading -----------------------------------------------------------------------------------
|
// --- Reading -----------------------------------------------------------------------------------
|
||||||
|
|
||||||
function onThisComputer(chat) {
|
export function onThisComputer(chat) {
|
||||||
return !!chat?.device && ctx.thisDevices().includes(chat.device.token_id)
|
return !!chat?.device && ctx.thisDevices().includes(chat.device.token_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,6 +552,7 @@ function renderHead(chat) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tools.append(activityButton())
|
||||||
tools.append(h("button", { type: "button", class: "tool", "aria-label": t("More"), title: t("More"), onclick: (e) => chatMenu(e.currentTarget, open?.chat || chat) }, icon(ICONS.more)))
|
tools.append(h("button", { type: "button", class: "tool", "aria-label": t("More"), title: t("More"), onclick: (e) => chatMenu(e.currentTarget, open?.chat || chat) }, icon(ICONS.more)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,13 +565,23 @@ async function chatModels(chat) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether this chat may have a team (`GET /chats/{chat}/team`): only a chat on the server. */
|
/**
|
||||||
|
* Whether this chat is offered subagents: `{ cap, own }` (the instance's most; whether only a model
|
||||||
|
* the person names could serve them), or null. The chat's own `subagents` where the server gives
|
||||||
|
* it (2.0.0-beta.7), else its team's word (`GET /chats/{chat}/team`) -- only a chat on the server.
|
||||||
|
*/
|
||||||
async function teamOffered(chat) {
|
async function teamOffered(chat) {
|
||||||
if (chat.kind !== "chat") return false
|
const said = chat.subagents
|
||||||
|
if (said !== undefined && said !== null) {
|
||||||
|
const offered = typeof said === "object" ? said.offered : said
|
||||||
|
return offered ? { cap: subagentsCap(said), own: namedOnly(said) } : null
|
||||||
|
}
|
||||||
|
if (chat.kind !== "chat") return null
|
||||||
try {
|
try {
|
||||||
return !!(await get(`/chats/${encodeURIComponent(chat.id)}/team`)).team?.offered
|
const team = (await get(`/chats/${encodeURIComponent(chat.id)}/team`)).team
|
||||||
|
return team?.offered ? { cap: subagentsCap(), own: false } : null
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,26 +651,31 @@ function effortChip(model, value, onChange) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The team's size: a chip saying it, opening the two fields. `value` { min, max } or empty. */
|
/**
|
||||||
function agentsChip(value, onChange) {
|
* The subagents a chat may run: a chip saying Min–Max, opening one range with two handles.
|
||||||
|
* `value` { min, max } or empty; `cap` the instance's most, where the server says it.
|
||||||
|
*/
|
||||||
|
function agentsChip(value, onChange, { cap, own } = {}) {
|
||||||
const said = value?.min || value?.max ? `${value.min ?? "…"}–${value.max ?? "…"}` : t("default")
|
const said = value?.min || value?.max ? `${value.min ?? "…"}–${value.max ?? "…"}` : t("default")
|
||||||
return chip({
|
return chip({
|
||||||
ico: ICONS.users,
|
ico: ICONS.users,
|
||||||
label: `${t("Agents")} ${said}`,
|
label: `${t("Subagents")} ${said}`,
|
||||||
title: t("Its own agent included. Above one, it can add members that do the work while it leads them."),
|
title: own ? SUBAGENTS_OWN() : SUBAGENTS_HINT(),
|
||||||
|
cls: `cchip--subagents${own ? " is-warn" : ""}`,
|
||||||
onclick: (e) => {
|
onclick: (e) => {
|
||||||
const fields = agentsControl({ value, id: "agents-min" })
|
const fields = agentsControl({ value, id: "agents-min", cap })
|
||||||
popover(
|
popover(
|
||||||
e.currentTarget,
|
e.currentTarget,
|
||||||
h(
|
h(
|
||||||
"div",
|
"div",
|
||||||
{ class: "pop__body" },
|
{ class: "pop__body" },
|
||||||
h("label", { class: "pop__title", for: "agents-min", text: t("Agents") }),
|
h("p", { class: "pop__title", text: t("Subagents") }),
|
||||||
fields,
|
fields,
|
||||||
h("p", { class: "hint", text: t("Its own agent included. Above one, it can add members that do the work while it leads them.") }),
|
own ? h("p", { class: "hint is-warn", text: SUBAGENTS_OWN() }) : null,
|
||||||
|
h("p", { class: "hint", text: SUBAGENTS_HINT() }),
|
||||||
h("div", { class: "pop__actions" }, h("button", { type: "button", class: "btn btn--sm btn--primary", text: t("Save"), onclick: () => (closePopover(), onChange(fields.value)) })),
|
h("div", { class: "pop__actions" }, h("button", { type: "button", class: "btn btn--sm btn--primary", text: t("Save"), onclick: () => (closePopover(), onChange(fields.value)) })),
|
||||||
),
|
),
|
||||||
{ up: true, label: t("Agents") },
|
{ up: true, label: t("Subagents") },
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -705,7 +721,7 @@ async function renderChips() {
|
|||||||
renderChips()
|
renderChips()
|
||||||
}, mayPick))
|
}, mayPick))
|
||||||
if (model) parts.push(effortChip(model, draft.effort, (l) => ((draft.effort = l), renderChips())))
|
if (model) parts.push(effortChip(model, draft.effort, (l) => ((draft.effort = l), renderChips())))
|
||||||
if (draft.kind === "chat" && teamPossible(model)) parts.push(agentsChip(draft.agents, (a) => ((draft.agents = a), renderChips())))
|
if (draft.kind === "chat" && teamPossible(model)) parts.push(agentsChip(draft.agents, (a) => ((draft.agents = a), renderChips()), { cap: subagentsCap(), own: namedOnly(model?.subagents) }))
|
||||||
box.replaceChildren(...parts.filter(Boolean))
|
box.replaceChildren(...parts.filter(Boolean))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -732,8 +748,9 @@ async function renderChips() {
|
|||||||
if (chat.model) parts.push(modelChip(models, chat.model, (id) => save({ model: id }), mayPick))
|
if (chat.model) parts.push(modelChip(models, chat.model, (id) => save({ model: id }), mayPick))
|
||||||
parts.push(effortChip(current, chat.effort || levelsOf(current).start, (l) => save({ effort: l })))
|
parts.push(effortChip(current, chat.effort || levelsOf(current).start, (l) => save({ effort: l })))
|
||||||
box.replaceChildren(...parts.filter(Boolean))
|
box.replaceChildren(...parts.filter(Boolean))
|
||||||
if (!(await teamOffered(chat)) || drawing !== chipsDrawn || open?.chat?.id !== chat.id) return
|
const offer = await teamOffered(chat)
|
||||||
box.append(agentsChip(open.chat.agents || chat.agents, (a) => save({ agents: a })))
|
if (!offer || drawing !== chipsDrawn || open?.chat?.id !== chat.id) return
|
||||||
|
box.append(agentsChip(open.chat.agents || chat.agents, (a) => save({ agents: a }), offer))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export const ICONS = {
|
|||||||
attach: "M7.25 2.5h1.5v4.75h4.75v1.5H8.75v4.75h-1.5V8.75H2.5v-1.5h4.75z",
|
attach: "M7.25 2.5h1.5v4.75h4.75v1.5H8.75v4.75h-1.5V8.75H2.5v-1.5h4.75z",
|
||||||
bolt: "M9.5 1L3 9.25h4.25L6.5 15 13 6.75H8.75z",
|
bolt: "M9.5 1L3 9.25h4.25L6.5 15 13 6.75H8.75z",
|
||||||
stop: "M4.5 4.5h7v7h-7z",
|
stop: "M4.5 4.5h7v7h-7z",
|
||||||
|
tasks: "M2 3.25h2v2H2zM5.5 3.5H14V5H5.5zM2 7h2v2H2zM5.5 7.25H14v1.5H5.5zM2 10.75h2v2H2zM5.5 11H14v1.5H5.5z",
|
||||||
back: "M10 3.5L5.5 8 10 12.5 11.1 11.4 7.7 8 11.1 4.6z",
|
back: "M10 3.5L5.5 8 10 12.5 11.1 11.4 7.7 8 11.1 4.6z",
|
||||||
terminal: "M1.5 2.5h13v11h-13zM3 4v8h10V4zM4.2 5.4l2.6 2.6-2.6 2.6-1-1L4.8 8 3.2 6.4zM7.5 10h4v1.5h-4z",
|
terminal: "M1.5 2.5h13v11h-13zM3 4v8h10V4zM4.2 5.4l2.6 2.6-2.6 2.6-1-1L4.8 8 3.2 6.4zM7.5 10h4v1.5h-4z",
|
||||||
search: "M7 2a5 5 0 013.95 8.07l3.24 3.24-1.06 1.06-3.24-3.24A5 5 0 117 2zm0 1.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z",
|
search: "M7 2a5 5 0 013.95 8.07l3.24 3.24-1.06 1.06-3.24-3.24A5 5 0 117 2zm0 1.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z",
|
||||||
|
|||||||
+8
-2
@@ -12,13 +12,14 @@ import { renderSettings, initSettings, scrollToSection } from "./settings.js"
|
|||||||
import { initViewer, onViewer, closeViewer } from "./viewer.js"
|
import { initViewer, onViewer, closeViewer } from "./viewer.js"
|
||||||
import { initExplorer, showLocal, showFilesTab } from "./explorer.js"
|
import { initExplorer, showLocal, showFilesTab } from "./explorer.js"
|
||||||
import { initList, refreshList, markCurrent } from "./list.js"
|
import { initList, refreshList, markCurrent } from "./list.js"
|
||||||
import { initChat, openChat, newChat, closeChat, openChatId, chatMenu, statusChanged, draftKind } from "./chat.js"
|
import { initChat, openChat, newChat, closeChat, openChatId, chatMenu, statusChanged, draftKind, onThisComputer } from "./chat.js"
|
||||||
import { initAttach, allowAttach } from "./attach.js"
|
import { initAttach, allowAttach } from "./attach.js"
|
||||||
import { initPalette, openPalette } from "./palette.js"
|
import { initPalette, openPalette } from "./palette.js"
|
||||||
import { initActivity, showPanel, togglePanel, panelOpen } from "./activity.js"
|
import { initActivity, showPanel, togglePanel, panelOpen } from "./activity.js"
|
||||||
import { initAgent, link } from "./agent.js"
|
import { initAgent, link } from "./agent.js"
|
||||||
import { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js"
|
import { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js"
|
||||||
import { closePopover } from "./popup.js"
|
import { closePopover } from "./popup.js"
|
||||||
|
import { noteSubagentsCap } from "./models.js"
|
||||||
|
|
||||||
let status = null
|
let status = null
|
||||||
let view = "" // "setup" | "app"
|
let view = "" // "setup" | "app"
|
||||||
@@ -118,7 +119,9 @@ function stopApp() {
|
|||||||
|
|
||||||
async function loadModels() {
|
async function loadModels() {
|
||||||
try {
|
try {
|
||||||
models = (await get("/models")).models || []
|
const r = await get("/models")
|
||||||
|
models = r.models || []
|
||||||
|
noteSubagentsCap(r.subagents_cap)
|
||||||
} catch {
|
} catch {
|
||||||
models = []
|
models = []
|
||||||
}
|
}
|
||||||
@@ -282,6 +285,9 @@ async function boot() {
|
|||||||
onLayout: (on) => {
|
onLayout: (on) => {
|
||||||
$("shell").dataset.activity = String(on)
|
$("shell").dataset.activity = String(on)
|
||||||
},
|
},
|
||||||
|
here: onThisComputer,
|
||||||
|
// The conversation as it now stands: the summary's divider, read again, if it is still open.
|
||||||
|
compacted: (id) => openChatId() === id && openChat(id),
|
||||||
})
|
})
|
||||||
|
|
||||||
$("new-chat-btn").prepend(icon(ICONS.plus))
|
$("new-chat-btn").prepend(icon(ICONS.plus))
|
||||||
|
|||||||
+92
-41
@@ -123,7 +123,7 @@ export function modelPicker({ id, models, value, onChange, label }) {
|
|||||||
return btn
|
return btn
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- How hard a model thinks, and how many agents a chat may have --------------------------------
|
// --- How hard a model thinks, and how many subagents a chat may have --------------------------------
|
||||||
//
|
//
|
||||||
// The levels are the harness spec's (3.2.0, `models/schema.json`), as /api/v2/models gives them:
|
// The levels are the harness spec's (3.2.0, `models/schema.json`), as /api/v2/models gives them:
|
||||||
// `levels` in offering order (`off` first where the model can switch thinking off, then its
|
// `levels` in offering order (`off` first where the model can switch thinking off, then its
|
||||||
@@ -193,69 +193,120 @@ export function effortControl({ model, value, onChange, id, compact }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a new chat on `model` may have a team: a chat on the server (an agent chat's team runs
|
* Whether a new chat on `model` is offered subagents: the model's `subagents.offered` in `/models`
|
||||||
* on its device), a model that calls tools and serves two requests at once (`parallel` null is a
|
* (server 2.0.0-beta.7: subagents on for the model and the instance, and at least one model could
|
||||||
* hosted API with no stated limit). The server decides again; this only keeps the fields away
|
* serve as one -- the model itself with room for two requests at once, or a usable model outside
|
||||||
* where they could never apply. An open chat asks the server (`GET /chats/{chat}/team`).
|
* its one-model-at-a-time server). A server that does not say it: a model that calls tools and
|
||||||
|
* serves two requests at once (`parallel` null is a hosted API with no stated limit). The server
|
||||||
|
* decides again; this only keeps the chip away where it could never apply. An open chat has its
|
||||||
|
* own `subagents` (chat.js).
|
||||||
*/
|
*/
|
||||||
export function teamPossible(model) {
|
export function teamPossible(model) {
|
||||||
|
const said = model?.subagents
|
||||||
|
if (said !== undefined && said !== null) return typeof said === "object" ? !!said.offered : !!said
|
||||||
return !!model?.tools && (model.parallel === null || model.parallel === undefined || model.parallel >= 2)
|
return !!model?.tools && (model.parallel === null || model.parallel === undefined || model.parallel >= 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether only a model the person names in the chat could serve its subagents (`named_only`). */
|
||||||
|
export function namedOnly(said) {
|
||||||
|
return !!(said && typeof said === "object" && said.named_only)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The instance's most, as `/models` said it last (`subagents_cap`); 32 where it says nothing.
|
||||||
|
export const SUBAGENTS_CAP = 32
|
||||||
|
let knownCap = null
|
||||||
|
export function noteSubagentsCap(n) {
|
||||||
|
knownCap = Number(n) >= 1 ? Number(n) : null
|
||||||
|
}
|
||||||
|
/** The most subagents a chat may have: the first of `sources` that says it (`cap`), else the instance's. */
|
||||||
|
export function subagentsCap(...sources) {
|
||||||
|
for (const s of sources) {
|
||||||
|
const n = Number(s?.cap)
|
||||||
|
if (Number.isFinite(n) && n >= 1) return n
|
||||||
|
}
|
||||||
|
return knownCap || SUBAGENTS_CAP
|
||||||
|
}
|
||||||
|
// The numbers count the chat's own agent: 1 is the agent alone.
|
||||||
|
export const SUBAGENTS_HINT = () => t("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.")
|
||||||
|
// The spec's `subagents.setting.own`: beside the setting where the chat's model cannot serve them.
|
||||||
|
export const SUBAGENTS_OWN = () => t("This model serves one request at a time: subagents need another model, on another server, named in the chat.")
|
||||||
|
|
||||||
|
// A new chat's setting as the harness spec seeds it (`loop.json` agents): 1 to 4.
|
||||||
|
export const AGENTS_DEFAULT = { min: 1, max: 4 }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The team's size: the fewest and the most agents, the chat's own agent included, as two number
|
* The agents a chat may run, its own agent counted: one range from 1 to `cap` with two handles,
|
||||||
* fields on one shared track. `value` is { min, max } (empty fields for a new chat: the person's
|
* Min and Max -- two range inputs overlaid on one track, the span between them filled. Each is a
|
||||||
* default for new chats then stands); `onChange({ min, max })` once a field is changed, with the
|
* real slider (arrow keys, Home/End, Page Up/Down), and Min never passes Max. `value` is
|
||||||
* empty one left out. The server checks the pair and says the bounds when it refuses one.
|
* { min, max } (empty for a new chat: the spec's defaults are shown, and left to the server
|
||||||
|
* unless a handle moves); `el.value` the pair. The server checks it again.
|
||||||
*/
|
*/
|
||||||
export function agentsControl({ value, onChange, id, compact }) {
|
export function agentsControl({ value, onChange, id, cap = SUBAGENTS_CAP }) {
|
||||||
const field = (which, v, label) =>
|
const top = Math.max(1, Number(cap) || SUBAGENTS_CAP)
|
||||||
|
const clamp = (n, d) => Math.min(top, Math.max(1, Number.isFinite(Number(n)) && n !== null && n !== "" ? Number(n) : d))
|
||||||
|
let given = value || {}
|
||||||
|
let touched = false
|
||||||
|
const handle = (which, label) =>
|
||||||
h("input", {
|
h("input", {
|
||||||
type: "number",
|
type: "range",
|
||||||
class: "agents__input",
|
class: `range2__input range2__input--${which}`,
|
||||||
id: which === "min" ? id : null,
|
id: which === "min" ? id : null,
|
||||||
min: "1",
|
min: "1",
|
||||||
|
max: String(top),
|
||||||
step: "1",
|
step: "1",
|
||||||
inputmode: "numeric",
|
|
||||||
value: v === undefined || v === null ? "" : String(v),
|
|
||||||
placeholder: compact ? null : t("default"),
|
|
||||||
"aria-label": label,
|
"aria-label": label,
|
||||||
title: label,
|
title: label,
|
||||||
})
|
})
|
||||||
const low = field("min", value?.min, t("Fewest"))
|
const low = handle("min", t("Min"))
|
||||||
const high = field("max", value?.max, t("Most"))
|
const high = handle("max", t("Max"))
|
||||||
const bounds = () => {
|
const said = h("output", { class: "range2__said", "aria-live": "polite", for: id || null })
|
||||||
high.min = low.value || "1"
|
const fill = h("span", { class: "range2__fill", "aria-hidden": "true" })
|
||||||
if (high.value) low.max = high.value
|
const box = h("span", { class: "range2__box" }, h("span", { class: "range2__track", "aria-hidden": "true" }, fill), low, high)
|
||||||
else low.removeAttribute("max")
|
const draw = () => {
|
||||||
|
const lo = Number(low.value)
|
||||||
|
const hi = Number(high.value)
|
||||||
|
const at = (n) => (top > 1 ? ((n - 1) / (top - 1)) * 100 : 0)
|
||||||
|
box.style.setProperty("--lo", `${at(lo)}%`)
|
||||||
|
box.style.setProperty("--hi", `${at(hi)}%`)
|
||||||
|
// Where the two meet, the one that can still move is on top: Min at the far end, else Max.
|
||||||
|
low.classList.toggle("is-top", lo === hi && hi === top)
|
||||||
|
low.setAttribute("aria-valuetext", `${t("Min")} ${lo}`)
|
||||||
|
high.setAttribute("aria-valuetext", `${t("Max")} ${hi}`)
|
||||||
|
said.textContent = `${t("Min")} ${lo} · ${t("Max")} ${hi}`
|
||||||
}
|
}
|
||||||
bounds()
|
const set = (v) => {
|
||||||
const read = () => {
|
const hi = clamp(v?.max, Math.min(AGENTS_DEFAULT.max, top))
|
||||||
const out = {}
|
const lo = Math.min(hi, clamp(v?.min, AGENTS_DEFAULT.min))
|
||||||
if (low.value) out.min = Number(low.value)
|
high.value = String(hi)
|
||||||
if (high.value) out.max = Number(high.value)
|
low.value = String(lo)
|
||||||
return out
|
draw()
|
||||||
}
|
}
|
||||||
for (const input of [low, high]) {
|
low.addEventListener("input", () => {
|
||||||
input.addEventListener("change", () => {
|
if (Number(low.value) > Number(high.value)) low.value = high.value
|
||||||
bounds()
|
touched = true
|
||||||
onChange?.(read())
|
draw()
|
||||||
})
|
})
|
||||||
}
|
high.addEventListener("input", () => {
|
||||||
|
if (Number(high.value) < Number(low.value)) high.value = low.value
|
||||||
|
touched = true
|
||||||
|
draw()
|
||||||
|
})
|
||||||
|
for (const input of [low, high]) input.addEventListener("change", () => onChange?.(read()))
|
||||||
|
const read = () => (touched || given.min || given.max ? { min: Number(low.value), max: Number(high.value) } : {})
|
||||||
|
set(given)
|
||||||
const el = h(
|
const el = h(
|
||||||
"span",
|
"span",
|
||||||
{ class: `agents${compact ? " agents--compact" : ""}`, role: "group", "aria-label": t("Agents"), title: t("Its own agent included. Above one, it can add members that do the work while it leads them.") },
|
{ class: "range2", role: "group", "aria-label": t("Subagents") },
|
||||||
compact ? h("span", { class: "agents__label", text: t("Agents") }) : null,
|
said,
|
||||||
low,
|
box,
|
||||||
h("span", { class: "agents__dash", "aria-hidden": "true", text: "–" }),
|
h("span", { class: "range2__ends", "aria-hidden": "true" }, h("span", { text: "1" }), h("span", { text: String(top) })),
|
||||||
high,
|
|
||||||
)
|
)
|
||||||
Object.defineProperty(el, "value", {
|
Object.defineProperty(el, "value", {
|
||||||
get: read,
|
get: read,
|
||||||
set: (v) => {
|
set: (v) => {
|
||||||
low.value = v?.min ?? ""
|
given = v || {}
|
||||||
high.value = v?.max ?? ""
|
touched = false
|
||||||
bounds()
|
set(given)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return el
|
return el
|
||||||
|
|||||||
+4
-4
@@ -341,7 +341,7 @@ async function drawTask(id) {
|
|||||||
fact(t("Last run"), statusChip(task.last_run, task.running)),
|
fact(t("Last run"), statusChip(task.last_run, task.running)),
|
||||||
fact(t("Model"), modelWords(models, task.model)),
|
fact(t("Model"), modelWords(models, task.model)),
|
||||||
fact(t("Reasoning effort"), h("span", { text: task.effort ? levelWord(task.effort) : t("The model's own") })),
|
fact(t("Reasoning effort"), h("span", { text: task.effort ? levelWord(task.effort) : t("The model's own") })),
|
||||||
fact(t("Agents"), h("span", { text: task.agents ? t("A team of up to %(n)s", { n: task.agents }) : t("No team") })),
|
fact(t("Subagents"), h("span", { text: task.agents ? t("Up to %(n)s at once", { n: task.agents }) : t("None") })),
|
||||||
fact(t("Tell me"), h("span", { text: NOTIFY[task.notify]?.() || NOTIFY.matters() })),
|
fact(t("Tell me"), h("span", { text: NOTIFY[task.notify]?.() || NOTIFY.matters() })),
|
||||||
fact(t("Runs"), h("span", { text: MODE[task.mode]?.() || MODE.fresh() })),
|
fact(t("Runs"), h("span", { text: MODE[task.mode]?.() || MODE.fresh() })),
|
||||||
),
|
),
|
||||||
@@ -582,15 +582,15 @@ async function drawForm(task) {
|
|||||||
const agents = h(
|
const agents = h(
|
||||||
"select",
|
"select",
|
||||||
{ id: "sched-agents", onchange: (e) => (draft.agents = Number(e.target.value) || null) },
|
{ id: "sched-agents", onchange: (e) => (draft.agents = Number(e.target.value) || null) },
|
||||||
h("option", { value: "", text: t("No team"), selected: !draft.agents }),
|
h("option", { value: "", text: t("None"), selected: !draft.agents }),
|
||||||
Array.from({ length: MAX_AGENTS - 1 }, (_, i) => i + 2).map((n) => h("option", { value: String(n), text: t("A team of up to %(n)s", { n }), selected: draft.agents === n })),
|
Array.from({ length: MAX_AGENTS - 1 }, (_, i) => i + 2).map((n) => h("option", { value: String(n), text: t("Up to %(n)s at once", { n }), selected: draft.agents === n })),
|
||||||
)
|
)
|
||||||
settings.replaceChildren(
|
settings.replaceChildren(
|
||||||
field(t("Tell me"), select("sched-notify", NOTIFY, "notify", () => (settings.querySelector("#sched-notify-hint").textContent = NOTIFY_HINT[draft.notify]())), NOTIFY_HINT[draft.notify]?.() || "", "sched-notify", "sched-notify-hint"),
|
field(t("Tell me"), select("sched-notify", NOTIFY, "notify", () => (settings.querySelector("#sched-notify-hint").textContent = NOTIFY_HINT[draft.notify]())), NOTIFY_HINT[draft.notify]?.() || "", "sched-notify", "sched-notify-hint"),
|
||||||
field(t("Runs"), select("sched-mode", MODE, "mode"), draft.mode === "continue" ? t("Each run sees the ones before it.") : t("Each run starts with nothing but the instruction."), "sched-mode"),
|
field(t("Runs"), select("sched-mode", MODE, "mode"), draft.mode === "continue" ? t("Each run sees the ones before it.") : t("Each run starts with nothing but the instruction."), "sched-mode"),
|
||||||
field(t("Model"), modelSelect, t("A busy model is waited for, then the next free one is used."), "sched-model"),
|
field(t("Model"), modelSelect, t("A busy model is waited for, then the next free one is used."), "sched-model"),
|
||||||
field(t("Reasoning effort"), effort, levels.length ? "" : t("Choose a model to choose its effort."), "sched-effort"),
|
field(t("Reasoning effort"), effort, levels.length ? "" : t("Choose a model to choose its effort."), "sched-effort"),
|
||||||
field(t("Agents"), agents, t("A team works on it together, its own agent included."), "sched-agents"),
|
field(t("Subagents"), agents, t("How many work on it at once, the run's own agent among them, on the same model."), "sched-agents"),
|
||||||
)
|
)
|
||||||
settings.querySelector("#sched-mode").addEventListener("change", drawSettings)
|
settings.querySelector("#sched-mode").addEventListener("change", drawSettings)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* LLeMbas v2.0.0-beta.6 (a71e9b4bdb8a) static/css/tokens.css, copied by scripts/fetch-tokens.sh: do not edit.
|
/* LLeMbas v2.0.0-beta.7 (e3bd54f4d210) static/css/tokens.css, copied by scripts/fetch-tokens.sh: do not edit.
|
||||||
The font-face rules are left out; the app bundles no font. */
|
The font-face rules are left out; the app bundles no font. */
|
||||||
/*
|
/*
|
||||||
Design tokens.
|
Design tokens.
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"tag": "v2.0.0-beta.6",
|
"tag": "v2.0.0-beta.7",
|
||||||
"commit": "a71e9b4bdb8acb6e9949e978253a72c347cc67f3",
|
"commit": "e3bd54f4d210cff6adb4586199caed4f41987f5e",
|
||||||
"files": {
|
"files": {
|
||||||
"cm-autocomplete.mjs": {"server": "26fe431a5f54e29952e094c93151f11473a80677bfb339711ecca08195a89df4", "copy": "fcb7fcbe4e2b072d031520ec898c81ac3f2f05fb4126e03003c9b5e60ffaa8de"},
|
"cm-autocomplete.mjs": {"server": "26fe431a5f54e29952e094c93151f11473a80677bfb339711ecca08195a89df4", "copy": "fcb7fcbe4e2b072d031520ec898c81ac3f2f05fb4126e03003c9b5e60ffaa8de"},
|
||||||
"cm-codemirror.mjs": {"server": "dea86a106e13c9316dfedfd29209780b853bdb2b477edf3f7c90f497b592a223", "copy": "0b80ae1289a1dc3e6718727e78bc757caad657e907d0351597867d4f90c36bd2"},
|
"cm-codemirror.mjs": {"server": "dea86a106e13c9316dfedfd29209780b853bdb2b477edf3f7c90f497b592a223", "copy": "0b80ae1289a1dc3e6718727e78bc757caad657e907d0351597867d4f90c36bd2"},
|
||||||
|
|||||||
Reference in new issue
Block a user