Compare commits
2
Commits
v1.0.0-beta.4
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ede91887f7
|
||
|
|
6bad293b29
|
No files matched your search
@@ -6,6 +6,26 @@ All notable changes to LLeMbas Desktop. The format follows
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- **Subagents**, by that name everywhere. The chip under the message box says **Subagents** with the
|
||||
fewest and the most a chat runs, and opens the two fields under those words; it 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
|
||||
|
||||
Carries LLeMbas CLI 2.0.0-beta.3 and the look of LLeMbas 2.0.0-beta.6. Scheduled tasks need LLeMbas
|
||||
|
||||
+217
-33
@@ -196,8 +196,6 @@ KEYS = (
|
||||
"Think before answering",
|
||||
"Fewest",
|
||||
"Most",
|
||||
"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",
|
||||
"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",
|
||||
"Open the activity panel",
|
||||
"Close the activity panel",
|
||||
"1 agent working",
|
||||
"%(n)s agents working",
|
||||
"%(n)s queued",
|
||||
"%(n)s waiting",
|
||||
"%(n)s finished",
|
||||
@@ -240,9 +236,7 @@ KEYS = (
|
||||
"Estimated: no reply has been counted yet.",
|
||||
"%(n)s tools offered.",
|
||||
"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.",
|
||||
"Team: %(size)s of %(max)s agents",
|
||||
"Show %(n)s more",
|
||||
"Room",
|
||||
"report",
|
||||
@@ -323,8 +317,6 @@ KEYS = (
|
||||
"A busy model is waited for, then the next free one is used.",
|
||||
"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 team of up to %(n)s",
|
||||
"A team works on it together, its own agent included.",
|
||||
"Choose a model to choose its effort.",
|
||||
"Due %(when)s",
|
||||
"Each run sees the ones before it.",
|
||||
@@ -333,7 +325,6 @@ KEYS = (
|
||||
"Edit %(title)s",
|
||||
"Every result, even with nothing to report.",
|
||||
"Models for scheduled tasks",
|
||||
"No team",
|
||||
"Only in its history.",
|
||||
"Open the chat",
|
||||
"Result",
|
||||
@@ -358,6 +349,31 @@ KEYS = (
|
||||
"Open its chat",
|
||||
"The model's own",
|
||||
"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",
|
||||
"Counted with the chat's own agent. 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
|
||||
@@ -365,6 +381,30 @@ KEYS = (
|
||||
# the device, as the server's catalogue has it.
|
||||
OWN: dict[str, dict[str, str]] = {
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Počítá se včetně vlastního agenta konverzace. Podagent běží na modelu konverzace, na jiném jen tehdy, když ho v konverzaci pojmenujete.",
|
||||
"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",
|
||||
"Minimise": "Minimalizovat",
|
||||
"Maximise": "Maximalizovat",
|
||||
@@ -532,8 +572,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 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 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.",
|
||||
"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í.",
|
||||
@@ -542,7 +580,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "Upravit %(title)s",
|
||||
"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",
|
||||
"No team": "Bez týmu",
|
||||
"Only in its history.": "Jen v její historii.",
|
||||
"Open the chat": "Otevřít konverzaci",
|
||||
"Result": "Výsledek",
|
||||
@@ -563,6 +600,30 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"No day ticked: every day.": "Žádný den nezaškrtnutý: každý den.",
|
||||
},
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Der eigene Agent des Chats zählt mit. Ein Subagent läuft auf dem Modell des Chats, auf einem anderen nur, wenn Sie es im Chat nennen.",
|
||||
"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ü",
|
||||
"Minimise": "Minimieren",
|
||||
"Maximise": "Maximieren",
|
||||
@@ -730,8 +791,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 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 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.",
|
||||
"Due %(when)s": "Fällig %(when)s",
|
||||
"Each run sees the ones before it.": "Jeder Lauf sieht die vorherigen.",
|
||||
@@ -740,7 +799,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "%(title)s bearbeiten",
|
||||
"Every result, even with nothing to report.": "Jedes Ergebnis, auch ohne Neuigkeiten.",
|
||||
"Models for scheduled tasks": "Modelle für geplante Aufgaben",
|
||||
"No team": "Kein Team",
|
||||
"Only in its history.": "Nur in ihrem Verlauf.",
|
||||
"Open the chat": "Chat öffnen",
|
||||
"Result": "Ergebnis",
|
||||
@@ -761,6 +819,30 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"No day ticked: every day.": "Kein Tag angehakt: jeden Tag.",
|
||||
},
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Cuenta también el agente propio del chat. Un subagente usa el modelo del chat, u otro solo si lo nombra en el chat.",
|
||||
"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ú",
|
||||
"Minimise": "Minimizar",
|
||||
"Maximise": "Maximizar",
|
||||
@@ -928,8 +1010,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 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 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.",
|
||||
"Due %(when)s": "Prevista %(when)s",
|
||||
"Each run sees the ones before it.": "Cada ejecución ve las anteriores.",
|
||||
@@ -938,7 +1018,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "Editar %(title)s",
|
||||
"Every result, even with nothing to report.": "Cada resultado, incluso sin nada que informar.",
|
||||
"Models for scheduled tasks": "Modelos para tareas programadas",
|
||||
"No team": "Sin equipo",
|
||||
"Only in its history.": "Solo en su historial.",
|
||||
"Open the chat": "Abrir el chat",
|
||||
"Result": "Resultado",
|
||||
@@ -959,6 +1038,30 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"No day ticked: every day.": "Ningún día marcado: todos los días.",
|
||||
},
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "L’agent de la conversation compte aussi. Un sous-agent utilise le modèle de la conversation, ou un autre seulement si vous le nommez dans la conversation.",
|
||||
"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",
|
||||
"Minimise": "Réduire",
|
||||
"Maximise": "Agrandir",
|
||||
@@ -1126,8 +1229,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 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 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.",
|
||||
"Due %(when)s": "Prévue %(when)s",
|
||||
"Each run sees the ones before it.": "Chaque exécution voit les précédentes.",
|
||||
@@ -1136,7 +1237,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "Modifier %(title)s",
|
||||
"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",
|
||||
"No team": "Pas d'équipe",
|
||||
"Only in its history.": "Seulement dans son historique.",
|
||||
"Open the chat": "Ouvrir la conversation",
|
||||
"Result": "Résultat",
|
||||
@@ -1157,6 +1257,30 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"No day ticked: every day.": "Aucun jour coché : tous les jours.",
|
||||
},
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "A beszélgetés saját ügynöke is beleszámít. Az alügynök a beszélgetés modelljén fut, másikon csak akkor, ha megnevezi a beszélgetésben.",
|
||||
"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ü",
|
||||
"Minimise": "Kis méret",
|
||||
"Maximise": "Teljes méret",
|
||||
@@ -1324,8 +1448,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 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 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.",
|
||||
"Due %(when)s": "Esedékes: %(when)s",
|
||||
"Each run sees the ones before it.": "Minden futás látja az előzőeket.",
|
||||
@@ -1334,7 +1456,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "%(title)s szerkesztése",
|
||||
"Every result, even with nothing to report.": "Minden eredmény, akkor is, ha nincs mit jelenteni.",
|
||||
"Models for scheduled tasks": "Modellek az ütemezett feladatokhoz",
|
||||
"No team": "Nincs csapat",
|
||||
"Only in its history.": "Csak az előzményeiben.",
|
||||
"Open the chat": "A csevegés megnyitása",
|
||||
"Result": "Eredmény",
|
||||
@@ -1355,6 +1476,30 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"No day ticked: every day.": "Ha egy nap sincs bejelölve: minden nap.",
|
||||
},
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Conta anche l’agente della chat. Un sottoagente usa il modello della chat, o un altro solo se lo nomini nella chat.",
|
||||
"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",
|
||||
"Minimise": "Riduci a icona",
|
||||
"Maximise": "Ingrandisci",
|
||||
@@ -1522,8 +1667,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 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 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.",
|
||||
"Due %(when)s": "Prevista %(when)s",
|
||||
"Each run sees the ones before it.": "Ogni esecuzione vede quelle precedenti.",
|
||||
@@ -1532,7 +1675,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "Modifica %(title)s",
|
||||
"Every result, even with nothing to report.": "Ogni risultato, anche senza nulla da segnalare.",
|
||||
"Models for scheduled tasks": "Modelli per le attività pianificate",
|
||||
"No team": "Nessun team",
|
||||
"Only in its history.": "Solo nella sua cronologia.",
|
||||
"Open the chat": "Apri la chat",
|
||||
"Result": "Risultato",
|
||||
@@ -1553,6 +1695,30 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"No day ticked: every day.": "Nessun giorno spuntato: ogni giorno.",
|
||||
},
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Liczony razem z własnym agentem czatu. Podagent działa na modelu czatu, na innym tylko wtedy, gdy wskażesz go w czacie.",
|
||||
"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",
|
||||
"Minimise": "Minimalizuj",
|
||||
"Maximise": "Maksymalizuj",
|
||||
@@ -1720,8 +1886,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 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 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.",
|
||||
"Due %(when)s": "Zaplanowane na %(when)s",
|
||||
"Each run sees the ones before it.": "Każde uruchomienie widzi poprzednie.",
|
||||
@@ -1730,7 +1894,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "Edytuj %(title)s",
|
||||
"Every result, even with nothing to report.": "Każdy wynik, nawet bez niczego do zgłoszenia.",
|
||||
"Models for scheduled tasks": "Modele dla zaplanowanych zadań",
|
||||
"No team": "Bez zespołu",
|
||||
"Only in its history.": "Tylko w jego historii.",
|
||||
"Open the chat": "Otwórz czat",
|
||||
"Result": "Wynik",
|
||||
@@ -1751,6 +1914,30 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"No day ticked: every day.": "Żaden dzień niezaznaczony: codziennie.",
|
||||
},
|
||||
"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.",
|
||||
"Counted with the chat's own agent. A subagent runs on the chat's model, or on another only when you name it in the chat.": "Počíta sa vrátane vlastného agenta konverzácie. Podagent beží na modeli konverzácie, na inom len vtedy, keď ho v konverzácii pomenujete.",
|
||||
"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",
|
||||
"Minimise": "Minimalizovať",
|
||||
"Maximise": "Maximalizovať",
|
||||
@@ -1918,8 +2105,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 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 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ť.",
|
||||
"Due %(when)s": "Naplánované na %(when)s",
|
||||
"Each run sees the ones before it.": "Každé spustenie vidí tie predchádzajúce.",
|
||||
@@ -1928,7 +2113,6 @@ OWN: dict[str, dict[str, str]] = {
|
||||
"Edit %(title)s": "Upraviť %(title)s",
|
||||
"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",
|
||||
"No team": "Bez tímu",
|
||||
"Only in its history.": "Len v jej histórii.",
|
||||
"Open the chat": "Otvoriť konverzáciu",
|
||||
"Result": "Výsledok",
|
||||
|
||||
+59
-18
@@ -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); }
|
||||
.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.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); }
|
||||
@@ -708,17 +709,19 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
||||
}
|
||||
.toggle input:checked { background: var(--accent); border-color: var(--accent); }
|
||||
.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, fewest and most: two number fields of one width with a dash between them, and in the header
|
||||
its word before them -- fixed tracks, so nothing moves when a number grows a digit. */
|
||||
.agents {
|
||||
.agents, .agents__heads {
|
||||
display: inline-grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
grid-template-columns: minmax(0, 1fr) var(--sp-3) minmax(0, 1fr);
|
||||
gap: var(--sp-2);
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: var(--control-h);
|
||||
}
|
||||
.agents__dash { color: var(--ink-muted); }
|
||||
.agents__dash { color: var(--ink-muted); text-align: center; }
|
||||
/* The words over the subagents' two fields, on the fields' own tracks. */
|
||||
.agents__heads { height: auto; font-size: var(--text-xs); color: var(--ink-muted); }
|
||||
.agents__input { width: 100%; min-width: 0; height: var(--control-h); padding: 0 var(--control-px-sm); }
|
||||
.agents--compact {
|
||||
grid-template-columns: auto calc(var(--w-field-xs) / 2) auto calc(var(--w-field-xs) / 2);
|
||||
@@ -920,6 +923,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__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); }
|
||||
/* "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--system { --ctx: var(--ink-muted); }
|
||||
.ctx--memory { --ctx: var(--warning); }
|
||||
@@ -943,15 +954,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
|
||||
(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__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__list { display: grid; gap: var(--sp-1); }
|
||||
.tasks__more > summary { cursor: pointer; padding: var(--sp-1) 0; color: var(--ink-muted); }
|
||||
.tasks__more[open] > summary { margin-bottom: 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__list { display: grid; gap: var(--sp-2); }
|
||||
/* 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__row {
|
||||
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);
|
||||
align-items: center;
|
||||
min-height: var(--control-h);
|
||||
@@ -960,17 +997,21 @@ details[open] > summary .step__chev .icon { transform: rotate(90deg); }
|
||||
list-style: 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.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__row:hover { background: var(--surface-hover); border-radius: var(--radius-lg); }
|
||||
.agent__what { display: grid; min-width: 0; }
|
||||
.agent__name, .agent__role { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.agent__name { color: var(--ink); }
|
||||
.agent__role { font-size: var(--text-xs); color: var(--ink-muted); }
|
||||
.agent__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ink); }
|
||||
.agent.is-done .agent__name, .agent.is-stopped .agent__name { 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__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 > .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); }
|
||||
|
||||
+192
-72
File diff suppressed because it is too large.
Load diff
+223
-76
@@ -1,14 +1,18 @@
|
||||
// The chat's activity panel, beside it: what is in its context, by category, against the model's
|
||||
// window; the files the chat and its agents wrote; every background agent -- a team's members as
|
||||
// a tree, helpers and commands -- with its state, how long it has run, its model, its latest step
|
||||
// and what it is writing, its whole conversation read-only and a button to stop it; the team's
|
||||
// room; and the plan. The strip above the message box sums the agents up and opens it. The same
|
||||
// facts as the server's own panel (`services/activity.py`), from /api/v2:
|
||||
// The chat's activity panel, beside it, opened from the header's button at any time: where an
|
||||
// agent chat works; what is in its context, by category, against the model's window, how far it
|
||||
// is to the automatic compaction and a button to compact now; the files the chat and its
|
||||
// subagents wrote; every background task -- subagents, a team's members as a tree under the one
|
||||
// that leads them, background commands -- with how long it has run and a stop square, unfolding
|
||||
// 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/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}/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
|
||||
// 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
|
||||
|
||||
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 stream = null // the events stream's handle
|
||||
let retry = 0 // the reconnect timer
|
||||
@@ -69,12 +75,14 @@ export function compact(value) {
|
||||
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) {
|
||||
if (!counts) return ""
|
||||
const parts = []
|
||||
if (counts.working === 1) parts.push(t("1 agent working"))
|
||||
else if (counts.working) parts.push(t("%(n)s agents working", { n: counts.working }))
|
||||
if (counts.working) parts.push(t("%(n)s running", { n: counts.working }))
|
||||
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.finished) parts.push(t("%(n)s finished", { n: counts.finished }))
|
||||
@@ -141,9 +149,10 @@ export function watchActivity(next) {
|
||||
cursor = 0
|
||||
readAt = contextAt = 0
|
||||
view = null
|
||||
compactError = ""
|
||||
openRows.clear()
|
||||
renderStrip()
|
||||
$("activity-open")?.setAttribute("aria-expanded", String(panelOpen()))
|
||||
markOpen()
|
||||
if (!chat) {
|
||||
if (panelOpen()) showPanel(false)
|
||||
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() {
|
||||
const strip = $("activity-strip")
|
||||
// A chip for every open chat: what runs, while something does, as the server's strip says it (a
|
||||
// chat whose helpers all finished long ago does not carry "12 finished" for ever: the panel
|
||||
// still lists them); else only the way into the panel.
|
||||
strip.hidden = !chat
|
||||
if (!chat) return clear(strip)
|
||||
const active = summary?.active ? summaryText(summary) : ""
|
||||
const text = active || t("Activity")
|
||||
// Only while something runs, as the server's own line: the header's button is the way into the
|
||||
// panel at any other time (and a chat whose tasks all finished long ago does not carry
|
||||
// "12 finished" for ever: the panel still lists them).
|
||||
const active = chat && summary?.active ? summaryText(summary) : ""
|
||||
strip.hidden = !active
|
||||
if (!active) return clear(strip)
|
||||
strip.replaceChildren(
|
||||
h(
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
id: "activity-open",
|
||||
class: `strip__btn${active ? " is-active" : ""}`,
|
||||
"aria-label": `${text} — ${panelOpen() ? t("Close the activity panel") : t("Open the activity panel")}`,
|
||||
id: "activity-strip-btn",
|
||||
class: "strip__btn is-active",
|
||||
"aria-label": `${active} — ${panelOpen() ? t("Close the activity panel") : t("Open the activity panel")}`,
|
||||
"aria-controls": "activity",
|
||||
"aria-expanded": String(panelOpen()),
|
||||
onclick: togglePanel,
|
||||
},
|
||||
h("span", { class: "dot", "aria-hidden": "true" }),
|
||||
h("span", { class: "strip__text", text }),
|
||||
h("span", { class: "strip__text", text: active }),
|
||||
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 ------------------------------------------------------------------------------------------
|
||||
|
||||
/** 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
|
||||
$("activity").hidden = !on
|
||||
layout(on)
|
||||
$("activity-open")?.setAttribute("aria-expanded", String(on))
|
||||
markOpen()
|
||||
clearInterval(ticker)
|
||||
ticker = 0
|
||||
if (!on) {
|
||||
view = null
|
||||
renderStrip()
|
||||
return
|
||||
}
|
||||
ticker = setInterval(tick, 1000)
|
||||
renderStrip()
|
||||
render()
|
||||
read(true)
|
||||
$("activity-close").focus()
|
||||
@@ -359,15 +392,43 @@ function render() {
|
||||
}
|
||||
// Kept where the reader was: the panel is drawn again on every change.
|
||||
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("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,
|
||||
)
|
||||
]
|
||||
body.replaceChildren(...parts.filter(Boolean))
|
||||
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) {
|
||||
if (!c) return h("p", { class: "activity__empty", text: t("Not measured.") })
|
||||
const known = c.percent !== null && c.percent !== undefined
|
||||
@@ -410,7 +471,67 @@ function contextPart(c) {
|
||||
{ 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 })}`,
|
||||
)
|
||||
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. */
|
||||
@@ -439,64 +560,71 @@ function outputsPart(outputs) {
|
||||
)
|
||||
}
|
||||
|
||||
function tasksPart() {
|
||||
const entries = data.agents || []
|
||||
function tasksPart(entries) {
|
||||
const team = data.team
|
||||
const box = h("div", { class: "tasks", "aria-live": "polite" })
|
||||
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
|
||||
}
|
||||
box.append(h("p", { class: "tasks__summary", text: summaryText(data.summary) || t("Nothing running.") }))
|
||||
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 }))
|
||||
}
|
||||
const live = entries.filter((e) => ACTIVE.includes(e.state))
|
||||
const ended = 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
|
||||
// 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) {
|
||||
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 (ended.length) box.append(fold("finished", t("%(n)s finished", { n: ended.length }), ended))
|
||||
if (team) {
|
||||
box.append(h("h3", { class: "activity__subheading", text: t("Room") }))
|
||||
box.append(
|
||||
room.length
|
||||
? h(
|
||||
"ol",
|
||||
{ class: "room" },
|
||||
room.map((m) =>
|
||||
h(
|
||||
"li",
|
||||
{ class: `room__entry${m.report ? " is-report" : ""}` },
|
||||
h(
|
||||
"div",
|
||||
{ class: "room__meta" },
|
||||
h("span", { class: "room__n", text: `#${m.n}` }),
|
||||
h("span", { class: "room__route", text: `${m.from} → ${(m.to || []).join(", ")}` }),
|
||||
m.report ? h("span", { class: "chip room__badge", text: t("report") }) : null,
|
||||
),
|
||||
h("div", { class: "room__text md" }, markdown(String(m.text || ""))),
|
||||
),
|
||||
),
|
||||
)
|
||||
: h("p", { class: "activity__empty", text: t("Nothing has been said in the room yet.") }),
|
||||
)
|
||||
}
|
||||
} else box.append(h("p", { class: "activity__empty", text: t("Nothing running.") }))
|
||||
if (ended.length) box.append(fold("finished", t("Finished %(n)s", { n: count(ended, false) }), ended, "tasks__finished"))
|
||||
return box
|
||||
}
|
||||
|
||||
/** A folded list of agents ("Show 3 more", "12 finished"), remembered open across redraws. */
|
||||
function fold(name, label, list) {
|
||||
const el = h("details", { class: "tasks__more", open: openRows.has(`fold:${name}`) }, h("summary", { text: label }), h("div", { class: "tasks__list" }, list.map(agentRows)))
|
||||
function roomPart() {
|
||||
if (!room.length) return h("p", { class: "activity__empty", text: t("Nothing has been said in the room yet.") })
|
||||
return h(
|
||||
"ol",
|
||||
{ class: "room" },
|
||||
room.map((m) =>
|
||||
h(
|
||||
"li",
|
||||
{ class: `room__entry${m.report ? " is-report" : ""}` },
|
||||
h(
|
||||
"div",
|
||||
{ class: "room__meta" },
|
||||
h("span", { class: "room__n", text: `#${m.n}` }),
|
||||
h("span", { class: "room__route", text: `${m.from} → ${(m.to || []).join(", ")}` }),
|
||||
m.report ? h("span", { class: "chip room__badge", text: t("report") }) : null,
|
||||
),
|
||||
h("div", { class: "room__text md" }, markdown(String(m.text || ""))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/** A folded list of tasks ("Show 3 more", "Finished 12", "Tasks: 2"), remembered across redraws. */
|
||||
function fold(name, label, list, cls = "") {
|
||||
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}`)))
|
||||
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) {
|
||||
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) {
|
||||
@@ -512,7 +640,7 @@ function agentRow(a) {
|
||||
"button",
|
||||
{
|
||||
type: "button",
|
||||
class: "pane__btn agent__stop",
|
||||
class: "agent__stop",
|
||||
"aria-label": t("Stop %(name)s", { name: a.name }),
|
||||
title: t("Stop %(name)s", { name: a.name }),
|
||||
onclick: (e) => {
|
||||
@@ -521,13 +649,15 @@ function agentRow(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(
|
||||
"p",
|
||||
{ class: "agent__facts" },
|
||||
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.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,
|
||||
@@ -547,8 +677,8 @@ function agentRow(a) {
|
||||
h(
|
||||
"summary",
|
||||
{ class: "agent__row" },
|
||||
h("span", { class: "agent__dot", "aria-hidden": "true" }),
|
||||
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__chev", "aria-hidden": "true" }, icon(ICONS.chevron)),
|
||||
h("span", { class: "agent__what", title: label(a) }, h("bdi", { class: "agent__name", text: label(a) })),
|
||||
time,
|
||||
stopBtn,
|
||||
),
|
||||
@@ -556,12 +686,11 @@ function agentRow(a) {
|
||||
"div",
|
||||
{ class: "agent__body" },
|
||||
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,
|
||||
step,
|
||||
a.text ? h("p", { class: "agent__text", text: a.text }) : null,
|
||||
a.transcript
|
||||
? h("button", { type: "button", class: "btn btn--sm", text: t("Open its conversation"), onclick: () => openAgent(a) })
|
||||
: null,
|
||||
a.transcript ? 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))))
|
||||
@@ -569,6 +698,22 @@ function agentRow(a) {
|
||||
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) {
|
||||
if (!chat) return
|
||||
if (a.kind === "member") {
|
||||
@@ -667,8 +812,10 @@ async function readAgent() {
|
||||
|
||||
// --- Start ------------------------------------------------------------------------------------------
|
||||
|
||||
export function initActivity({ onLayout }) {
|
||||
export function initActivity({ onLayout, here, compacted }) {
|
||||
layout = onLayout || layout
|
||||
onThisComputer = here || onThisComputer
|
||||
onCompacted = compacted || onCompacted
|
||||
$("activity-close").replaceChildren(icon(ICONS.close))
|
||||
$("activity-close").addEventListener("click", () => showPanel(false))
|
||||
$("activity-back").replaceChildren(icon(ICONS.back))
|
||||
|
||||
+36
-18
@@ -13,9 +13,9 @@ import { markdown } from "./markdown.js"
|
||||
import { openViewer, showKind } from "./viewer.js"
|
||||
import { readLocal } from "./explorer.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 { watchActivity, activityChanged, planList } from "./activity.js"
|
||||
import { watchActivity, activityChanged, planList, activityButton } from "./activity.js"
|
||||
import { agentBlock, agentReady, agentState, onAgentChange } from "./agent.js"
|
||||
import { folderName } from "./list.js"
|
||||
|
||||
@@ -30,7 +30,7 @@ export function openChatId() {
|
||||
|
||||
// --- Reading -----------------------------------------------------------------------------------
|
||||
|
||||
function onThisComputer(chat) {
|
||||
export function onThisComputer(chat) {
|
||||
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)))
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
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 {
|
||||
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 {
|
||||
return false
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,26 +651,32 @@ 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 the fewest and the most, opening the two fields.
|
||||
* `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")
|
||||
return chip({
|
||||
ico: ICONS.users,
|
||||
label: `${t("Agents")} ${said}`,
|
||||
title: t("Its own agent included. Above one, it can add members that do the work while it leads them."),
|
||||
label: `${t("Subagents")} ${said}`,
|
||||
title: own ? SUBAGENTS_OWN() : SUBAGENTS_HINT(),
|
||||
cls: `cchip--subagents${own ? " is-warn" : ""}`,
|
||||
onclick: (e) => {
|
||||
const fields = agentsControl({ value, id: "agents-min" })
|
||||
const fields = agentsControl({ value, id: "agents-min", cap })
|
||||
popover(
|
||||
e.currentTarget,
|
||||
h(
|
||||
"div",
|
||||
{ class: "pop__body" },
|
||||
h("label", { class: "pop__title", for: "agents-min", text: t("Agents") }),
|
||||
h("label", { class: "pop__title", for: "agents-min", text: t("Subagents") }),
|
||||
h("div", { class: "agents__heads", "aria-hidden": "true" }, h("span", { text: t("Fewest") }), h("span"), h("span", { text: t("Most") })),
|
||||
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)) })),
|
||||
),
|
||||
{ up: true, label: t("Agents") },
|
||||
{ up: true, label: t("Subagents") },
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -705,7 +722,7 @@ async function renderChips() {
|
||||
renderChips()
|
||||
}, mayPick))
|
||||
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))
|
||||
return
|
||||
}
|
||||
@@ -732,8 +749,9 @@ async function renderChips() {
|
||||
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 })))
|
||||
box.replaceChildren(...parts.filter(Boolean))
|
||||
if (!(await teamOffered(chat)) || drawing !== chipsDrawn || open?.chat?.id !== chat.id) return
|
||||
box.append(agentsChip(open.chat.agents || chat.agents, (a) => save({ agents: a })))
|
||||
const offer = await teamOffered(chat)
|
||||
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",
|
||||
bolt: "M9.5 1L3 9.25h4.25L6.5 15 13 6.75H8.75z",
|
||||
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",
|
||||
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",
|
||||
|
||||
+8
-2
@@ -12,13 +12,14 @@ import { renderSettings, initSettings, scrollToSection } from "./settings.js"
|
||||
import { initViewer, onViewer, closeViewer } from "./viewer.js"
|
||||
import { initExplorer, showLocal, showFilesTab } from "./explorer.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 { initPalette, openPalette } from "./palette.js"
|
||||
import { initActivity, showPanel, togglePanel, panelOpen } from "./activity.js"
|
||||
import { initAgent, link } from "./agent.js"
|
||||
import { initScheduled, showScheduled, scheduledCommands } from "./scheduled.js"
|
||||
import { closePopover } from "./popup.js"
|
||||
import { noteSubagentsCap } from "./models.js"
|
||||
|
||||
let status = null
|
||||
let view = "" // "setup" | "app"
|
||||
@@ -118,7 +119,9 @@ function stopApp() {
|
||||
|
||||
async function loadModels() {
|
||||
try {
|
||||
models = (await get("/models")).models || []
|
||||
const r = await get("/models")
|
||||
models = r.models || []
|
||||
noteSubagentsCap(r.subagents_cap)
|
||||
} catch {
|
||||
models = []
|
||||
}
|
||||
@@ -282,6 +285,9 @@ async function boot() {
|
||||
onLayout: (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))
|
||||
|
||||
+42
-14
@@ -123,7 +123,7 @@ export function modelPicker({ id, models, value, onChange, label }) {
|
||||
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:
|
||||
// `levels` in offering order (`off` first where the model can switch thinking off, then its
|
||||
@@ -193,28 +193,57 @@ 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
|
||||
* on its device), 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 fields away
|
||||
* where they could never apply. An open chat asks the server (`GET /chats/{chat}/team`).
|
||||
* Whether a new chat on `model` is offered subagents: the model's `subagents.offered` in `/models`
|
||||
* (server 2.0.0-beta.7: subagents on for the model and the instance, and at least one model could
|
||||
* serve as one -- the model itself with room for two requests at once, or a usable model outside
|
||||
* 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) {
|
||||
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)
|
||||
}
|
||||
|
||||
/** 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
|
||||
}
|
||||
export const SUBAGENTS_HINT = () => t("Counted with the chat's own agent. 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.")
|
||||
|
||||
/**
|
||||
* The team's size: the fewest and the most agents, the chat's own agent included, as two number
|
||||
* fields on one shared track. `value` is { min, max } (empty fields for a new chat: the person's
|
||||
* default for new chats then stands); `onChange({ min, max })` once a field is changed, with the
|
||||
* empty one left out. The server checks the pair and says the bounds when it refuses one.
|
||||
* The subagents a chat may run: the fewest and the most, as two number fields on one shared
|
||||
* track. `value` is { min, max } (empty fields for a new chat: the server's defaults then stand);
|
||||
* `cap` the instance's most; `onChange({ min, max })` once a field is changed, with the empty one
|
||||
* left out. The server checks the pair and says the bounds when it refuses one.
|
||||
*/
|
||||
export function agentsControl({ value, onChange, id, compact }) {
|
||||
export function agentsControl({ value, onChange, id, compact, cap = SUBAGENTS_CAP }) {
|
||||
const field = (which, v, label) =>
|
||||
h("input", {
|
||||
type: "number",
|
||||
class: "agents__input",
|
||||
id: which === "min" ? id : null,
|
||||
min: "1",
|
||||
max: String(cap),
|
||||
step: "1",
|
||||
inputmode: "numeric",
|
||||
value: v === undefined || v === null ? "" : String(v),
|
||||
@@ -226,8 +255,7 @@ export function agentsControl({ value, onChange, id, compact }) {
|
||||
const high = field("max", value?.max, t("Most"))
|
||||
const bounds = () => {
|
||||
high.min = low.value || "1"
|
||||
if (high.value) low.max = high.value
|
||||
else low.removeAttribute("max")
|
||||
low.max = high.value || String(cap)
|
||||
}
|
||||
bounds()
|
||||
const read = () => {
|
||||
@@ -244,8 +272,8 @@ export function agentsControl({ value, onChange, id, compact }) {
|
||||
}
|
||||
const el = h(
|
||||
"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.") },
|
||||
compact ? h("span", { class: "agents__label", text: t("Agents") }) : null,
|
||||
{ class: `agents${compact ? " agents--compact" : ""}`, role: "group", "aria-label": t("Subagents"), title: SUBAGENTS_HINT() },
|
||||
compact ? h("span", { class: "agents__label", text: t("Subagents") }) : null,
|
||||
low,
|
||||
h("span", { class: "agents__dash", "aria-hidden": "true", text: "–" }),
|
||||
high,
|
||||
|
||||
+4
-4
@@ -341,7 +341,7 @@ async function drawTask(id) {
|
||||
fact(t("Last run"), statusChip(task.last_run, task.running)),
|
||||
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("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("Runs"), h("span", { text: MODE[task.mode]?.() || MODE.fresh() })),
|
||||
),
|
||||
@@ -582,15 +582,15 @@ async function drawForm(task) {
|
||||
const agents = h(
|
||||
"select",
|
||||
{ id: "sched-agents", onchange: (e) => (draft.agents = Number(e.target.value) || null) },
|
||||
h("option", { value: "", text: t("No team"), 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 })),
|
||||
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("Up to %(n)s at once", { n }), selected: draft.agents === n })),
|
||||
)
|
||||
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("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("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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user