@anchrd/intel-ui 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/agent/agent-calendar/agent-calendar.tsx +42 -4
- package/src/agent/agent-costs/agent-costs.ts +95 -0
- package/src/agent/agent-delegation-notice/agent-delegation-notice.ts +48 -0
- package/src/agent/agent-entry-title/agent-entry-title.ts +48 -7
- package/src/agent/agent-log/agent-log.tsx +127 -11
- package/src/agent/agent-models/agent-models.ts +31 -67
- package/src/agent/agent-models/agent-models.types.ts +11 -5
- package/src/agent/agent-profile/agent-profile.tsx +238 -65
- package/src/agent/agent-state/agent-state.ts +17 -0
- package/src/agent/agent-status-dot/agent-status-dot.ts +73 -0
- package/src/agent/agent.tsx +159 -50
- package/src/app/app-tree/app-tree.tsx +9 -3
- package/src/app/settings-dialog/settings-dialog.tsx +22 -31
- package/src/components/ui/popover.tsx +41 -0
- package/src/data/agent-runtime/agent-runtime.ts +26 -0
- package/src/data/intel-data-provider/intel-data-provider.ts +20 -0
- package/src/data/intel-data-provider/intel-data-provider.types.ts +19 -0
- package/src/entry-picker/entry-picker.tsx +16 -5
- package/src/hooks/use-model-catalog.ts +26 -0
- package/src/i18n/de.json +32 -10
- package/src/i18n/en.json +32 -10
- package/src/i18n/es.json +32 -10
- package/src/section-hint/section-hint.tsx +40 -0
- package/src/timezone/timezone-combobox/timezone-combobox.tsx +149 -0
- package/src/timezone/timezone-context.tsx +25 -5
- package/src/timezone/timezone.ts +119 -5
- package/src/title-row/title-row.tsx +9 -4
- package/src/user-name/user-name.ts +41 -0
- package/tsconfig.json +7 -1
package/src/i18n/de.json
CHANGED
|
@@ -282,12 +282,13 @@
|
|
|
282
282
|
"agent.tab.chat": "Chat",
|
|
283
283
|
"agent.tab.calendar": "Kalender",
|
|
284
284
|
"agent.tab.log": "Protokoll",
|
|
285
|
-
"agent.
|
|
286
|
-
"agent.
|
|
287
|
-
"agent.
|
|
288
|
-
"agent.
|
|
285
|
+
"agent.status.running": "Läuft",
|
|
286
|
+
"agent.status.waiting": "Wartet auf den nächsten Lauf",
|
|
287
|
+
"agent.status.failed": "Letzter Lauf fehlgeschlagen",
|
|
288
|
+
"agent.status.paused": "Pausiert",
|
|
289
|
+
"agent.status.unknown": "Laufzeit nicht erreichbar",
|
|
289
290
|
"agent.nextRun": "nächster Lauf {when} ({zone})",
|
|
290
|
-
"agent.
|
|
291
|
+
"agent.lastRun": "letzter Lauf {when}",
|
|
291
292
|
"agent.pausedNoRuns": "kein Zeitplan feuert, keine Aufgabe und keine Mail wird angenommen; der Chat antwortet weiter",
|
|
292
293
|
"agent.pause": "Pausieren",
|
|
293
294
|
"agent.resume": "Fortsetzen",
|
|
@@ -296,6 +297,7 @@
|
|
|
296
297
|
"agent.runNowPaused": "Setze den Agenten fort, bevor du ihn ausführst.",
|
|
297
298
|
"agent.actionFailed": "Das hat nicht funktioniert. Versuche es erneut.",
|
|
298
299
|
"agent.keyMissing": "Dieser Agent hat keinen Application-Key, deshalb kann kein Lauf starten. Ersetze seinen Key unter „Identität“ im Profil.",
|
|
300
|
+
"agent.createdBy": "von {name}",
|
|
299
301
|
"agent.notPermitted": "Du darfst diesen Agenten lesen, aber nicht steuern. Dafür braucht es die Berechtigung „Agenten ausführen“.",
|
|
300
302
|
"agent.noRuntime": "Diese Installation betreibt keine Agenten-Laufzeit, deshalb kann dieser Agent hier weder chatten noch laufen. Seine Definition bleibt erhalten und funktioniert wieder auf einer Installation, die eine hat.",
|
|
301
303
|
"agent.loadFailed": "Dieser Agent konnte nicht geladen werden. Prüfe deinen Zugriff und versuche es erneut.",
|
|
@@ -320,10 +322,10 @@
|
|
|
320
322
|
"agent.contactAdd": "Erreichbarkeit hinzufügen",
|
|
321
323
|
"agent.contactAddReason": "Eine Mail-Adresse gehört der Installation, nicht der Definition des Agenten — sie wird dort konfiguriert, wo die Laufzeit betrieben wird.",
|
|
322
324
|
"agent.knows": "Was er weiß",
|
|
323
|
-
"agent.knowsHint": "
|
|
324
|
-
"agent.addReference": "
|
|
325
|
+
"agent.knowsHint": "Was dieser Agent liest, und wofür jeder Eintrag zählt. Eine Systemnachricht weist ihn an — ein Ordner, ein Dokument oder eine Tabelle. Semantischer Kontext wird durchsucht und in das Gedächtnis wird zurückgeschrieben, deshalb sind beide Ordner.",
|
|
326
|
+
"agent.addReference": "Etwas zum Lesen hinzufügen",
|
|
325
327
|
"agent.removeReferenceOf": "{title} nicht mehr lesen",
|
|
326
|
-
"agent.
|
|
328
|
+
"agent.pickEntry": "Was gelesen wird",
|
|
327
329
|
"agent.roleFor": "Wofür {title} zählt",
|
|
328
330
|
"agent.role.system-message": "Systemnachricht",
|
|
329
331
|
"agent.role.semantic-context": "Semantischer Kontext",
|
|
@@ -335,11 +337,12 @@
|
|
|
335
337
|
"agent.removeToolServerOf": "{name} diesem Agenten wegnehmen",
|
|
336
338
|
"agent.toolServerUnavailable": "Du erreichst diesen Server nicht mehr",
|
|
337
339
|
"agent.toolsDelegationNotice": "Wer diesen Agenten ausführen darf, handelt auf deiner Verbindung.",
|
|
340
|
+
"agent.toolsDelegationNoticeBy": "Wer diesen Agenten ausführen darf, handelt auf der Verbindung von {name}.",
|
|
341
|
+
"agent.toolsDelegationNoticeSomebody": "Wer diesen Agenten ausführen darf, handelt auf der Verbindung dessen, der ihm diese Werkzeuge gegeben hat — nicht auf deiner.",
|
|
338
342
|
"agent.toolsNotConnected": "Du bist noch nicht am Firmenportal angemeldet, es gibt also nichts abzugeben. Öffne „Werkzeuge“, um dich zu verbinden.",
|
|
339
343
|
"agent.toolsNoneLeft": "Jeder Server, den du erreichst, ist diesem Agenten bereits gegeben.",
|
|
340
344
|
"agent.schedule": "Zeitplan",
|
|
341
345
|
"agent.scheduleHint": "Wann dieser Agent von sich aus handelt und was er ausführt. Jeder Zeitplan behält die Zeitzone, die er bekommen hat — er meint damit für alle Lesenden dieselbe Stunde.",
|
|
342
|
-
"agent.scheduleEmpty": "Dieser Agent läuft nur, wenn er gefragt wird.",
|
|
343
346
|
"agent.addSchedule": "Zeitplan hinzufügen",
|
|
344
347
|
"agent.removeSchedule": "Diesen Zeitplan entfernen",
|
|
345
348
|
"agent.scheduleTarget": "Was ausgeführt wird",
|
|
@@ -350,8 +353,23 @@
|
|
|
350
353
|
"agent.model": "Modell",
|
|
351
354
|
"agent.modelHint": "Welches Modell denkt. Eine Änderung übernimmt die Laufzeit innerhalb einer Minute.",
|
|
352
355
|
"agent.modelFigures": "{context} Kontext · {input} $ rein / {output} $ raus je 1 Mio. Token",
|
|
356
|
+
"agent.usage": "{input} rein · {output} raus · {cacheRead} Cache gelesen · {cacheWrite} Cache geschrieben",
|
|
357
|
+
"agent.usageUnknown": "Für diesen Lauf sind keine Verbrauchszahlen erfasst",
|
|
358
|
+
"agent.usageNotReported": "Dieser Anbieter meldet keinen Verbrauch",
|
|
359
|
+
"agent.costMissing": "Kosten stehen nicht im Gateway-Log",
|
|
360
|
+
"agent.costNotConfigured": "Keine Kostenangabe: Diese Installation hat kein Lese-Token für das AI Gateway.",
|
|
361
|
+
"agent.costUnreadable": "Keine Kostenangabe: Das AI-Gateway-Log war nicht lesbar.",
|
|
362
|
+
"agent.costPartial": "Das Gateway-Log wurde bis zur Seitengrenze gelesen — diese Summen sind eine Untergrenze.",
|
|
363
|
+
"agent.costWindow": "Letzte {days} Tage",
|
|
364
|
+
"agent.costWindowCalls": "{count} Modellaufrufe",
|
|
365
|
+
"agent.costs": "Kosten",
|
|
366
|
+
"agent.costsSpent": "{amount} in den letzten {days} Tagen",
|
|
367
|
+
"agent.costsSpentPast": "Bereits ausgegeben, für vergangene Läufe — keine Vorhersage für das oben gewählte Modell.",
|
|
368
|
+
"agent.costsSpentWith": "mit {models}",
|
|
369
|
+
"agent.modelFiguresStale": "Diese Angaben stammen aus einer eingebauten Tabelle und können veraltet sein.",
|
|
353
370
|
"agent.calendar": "Kommende Läufe",
|
|
354
|
-
"agent.calendarZones": "Jede Zeit steht in der Zeitzone ihres eigenen Zeitplans, denn dann läuft der Agent.",
|
|
371
|
+
"agent.calendarZones": "Jede Zeit steht in der Zeitzone ihres eigenen Zeitplans, denn dann läuft der Agent. Weicht deine eigene Zeitzone ab, steht sie daneben.",
|
|
372
|
+
"agent.calendarYourTime": "{when} bei dir",
|
|
355
373
|
"agent.calendarUnreadable": "Keiner der Zeitpläne dieses Agenten lässt sich als Cron-Ausdruck lesen, es ist also nichts geplant.",
|
|
356
374
|
"agent.log": "Läufe",
|
|
357
375
|
"agent.logEmpty": "Dieser Agent ist noch nicht gelaufen.",
|
|
@@ -379,6 +397,10 @@
|
|
|
379
397
|
"settings.appearance.dark": "Dunkel",
|
|
380
398
|
"settings.timezone": "Zeitzone",
|
|
381
399
|
"settings.timezoneHint": "Zum Lesen von Zeiten, und als Vorschlag, wenn du einem Agenten einen Zeitplan gibst. Ein Zeitplan behält die Zeitzone, die er bekommen hat — eine Reise verschiebt also keine Läufe, die du bereits gesetzt hast.",
|
|
400
|
+
"settings.timezoneSearch": "Stadt, Zone oder Versatz suchen …",
|
|
401
|
+
"settings.timezoneNoMatch": "Keine Zeitzone passt.",
|
|
402
|
+
"settings.timezoneOwn": "Dein Gerät",
|
|
403
|
+
"settings.timezoneAll": "Alle Zeitzonen",
|
|
382
404
|
"auth.signOut": "Abmelden",
|
|
383
405
|
"auth.signOutFailed": "Das Abmelden ist fehlgeschlagen. Prüfe deine Verbindung und versuche es erneut.",
|
|
384
406
|
"signIn.refusedTitle": "Angemeldet, aber nicht angenommen",
|
package/src/i18n/en.json
CHANGED
|
@@ -282,12 +282,13 @@
|
|
|
282
282
|
"agent.tab.chat": "Chat",
|
|
283
283
|
"agent.tab.calendar": "Calendar",
|
|
284
284
|
"agent.tab.log": "Log",
|
|
285
|
-
"agent.
|
|
286
|
-
"agent.
|
|
287
|
-
"agent.
|
|
288
|
-
"agent.
|
|
285
|
+
"agent.status.running": "Running",
|
|
286
|
+
"agent.status.waiting": "Waiting for its next run",
|
|
287
|
+
"agent.status.failed": "Last run failed",
|
|
288
|
+
"agent.status.paused": "Paused",
|
|
289
|
+
"agent.status.unknown": "Runtime not reachable",
|
|
289
290
|
"agent.nextRun": "next run {when} ({zone})",
|
|
290
|
-
"agent.
|
|
291
|
+
"agent.lastRun": "last run {when}",
|
|
291
292
|
"agent.pausedNoRuns": "no schedule fires, no task and no mail is taken; the chat still answers",
|
|
292
293
|
"agent.pause": "Pause",
|
|
293
294
|
"agent.resume": "Resume",
|
|
@@ -296,6 +297,7 @@
|
|
|
296
297
|
"agent.runNowPaused": "Resume the agent before running it.",
|
|
297
298
|
"agent.actionFailed": "That did not work. Try again.",
|
|
298
299
|
"agent.keyMissing": "This agent has no application key, so no run can start. Replace its key under Identity in the profile.",
|
|
300
|
+
"agent.createdBy": "by {name}",
|
|
299
301
|
"agent.notPermitted": "You may read this agent, but not drive it. Driving an agent needs the “run agents” permission.",
|
|
300
302
|
"agent.noRuntime": "This installation runs no agent runtime, so this agent cannot chat or run here. Its definition is kept and works again on an installation that has one.",
|
|
301
303
|
"agent.loadFailed": "This agent could not be loaded. Check your access and try again.",
|
|
@@ -320,10 +322,10 @@
|
|
|
320
322
|
"agent.contactAdd": "Add contact",
|
|
321
323
|
"agent.contactAddReason": "A mail address belongs to the installation, not to the agent's definition, so it is configured where the runtime is deployed.",
|
|
322
324
|
"agent.knows": "What it knows",
|
|
323
|
-
"agent.knowsHint": "
|
|
324
|
-
"agent.addReference": "Add
|
|
325
|
+
"agent.knowsHint": "What this agent reads, and what each entry counts as. A system message instructs it — a folder, a document or a table. Semantic context is searched and memory is written back to, so both of those are folders.",
|
|
326
|
+
"agent.addReference": "Add something to read",
|
|
325
327
|
"agent.removeReferenceOf": "Stop reading {title}",
|
|
326
|
-
"agent.
|
|
328
|
+
"agent.pickEntry": "What to read",
|
|
327
329
|
"agent.roleFor": "What {title} counts as",
|
|
328
330
|
"agent.role.system-message": "System message",
|
|
329
331
|
"agent.role.semantic-context": "Semantic context",
|
|
@@ -335,11 +337,12 @@
|
|
|
335
337
|
"agent.removeToolServerOf": "Take {name} away from this agent",
|
|
336
338
|
"agent.toolServerUnavailable": "You no longer reach this server",
|
|
337
339
|
"agent.toolsDelegationNotice": "Anyone who may run this agent acts on your connection.",
|
|
340
|
+
"agent.toolsDelegationNoticeBy": "Anyone who may run this agent acts on the connection of {name}.",
|
|
341
|
+
"agent.toolsDelegationNoticeSomebody": "Anyone who may run this agent acts on the connection of whoever gave it these tools — not on yours.",
|
|
338
342
|
"agent.toolsNotConnected": "You are not signed in to the company portal yet, so there is nothing to give away. Open Tools to connect.",
|
|
339
343
|
"agent.toolsNoneLeft": "Every server you reach is already given to this agent.",
|
|
340
344
|
"agent.schedule": "Schedule",
|
|
341
345
|
"agent.scheduleHint": "When this agent acts on its own, and what it runs. Each schedule keeps the timezone it was given, so it means the same hour to everybody reading it.",
|
|
342
|
-
"agent.scheduleEmpty": "This agent runs only when it is asked.",
|
|
343
346
|
"agent.addSchedule": "Add schedule",
|
|
344
347
|
"agent.removeSchedule": "Remove this schedule",
|
|
345
348
|
"agent.scheduleTarget": "What to run",
|
|
@@ -350,8 +353,23 @@
|
|
|
350
353
|
"agent.model": "Model",
|
|
351
354
|
"agent.modelHint": "Which model does the thinking. A change is picked up by the runtime within a minute.",
|
|
352
355
|
"agent.modelFigures": "{context} context · ${input} in / ${output} out per 1M tokens",
|
|
356
|
+
"agent.usage": "{input} in · {output} out · {cacheRead} cache read · {cacheWrite} cache written",
|
|
357
|
+
"agent.usageUnknown": "No usage figures for this run",
|
|
358
|
+
"agent.usageNotReported": "This provider reports no usage",
|
|
359
|
+
"agent.costMissing": "Cost not in the gateway log",
|
|
360
|
+
"agent.costNotConfigured": "No cost figures: this installation has no AI Gateway read token.",
|
|
361
|
+
"agent.costUnreadable": "No cost figures: the AI Gateway log could not be read.",
|
|
362
|
+
"agent.costPartial": "The gateway log was read up to its page limit, so these totals are a lower bound.",
|
|
363
|
+
"agent.costWindow": "Last {days} days",
|
|
364
|
+
"agent.costWindowCalls": "{count} model calls",
|
|
365
|
+
"agent.costs": "Cost",
|
|
366
|
+
"agent.costsSpent": "{amount} in the last {days} days",
|
|
367
|
+
"agent.costsSpentPast": "Already spent, on past runs — not a forecast for the model chosen above.",
|
|
368
|
+
"agent.costsSpentWith": "with {models}",
|
|
369
|
+
"agent.modelFiguresStale": "These figures come from a built-in table and can be out of date.",
|
|
353
370
|
"agent.calendar": "Upcoming runs",
|
|
354
|
-
"agent.calendarZones": "Each time is shown in the timezone of its own schedule, because that is when the agent runs.",
|
|
371
|
+
"agent.calendarZones": "Each time is shown in the timezone of its own schedule, because that is when the agent runs. Where your own timezone differs, it is added beside it.",
|
|
372
|
+
"agent.calendarYourTime": "{when} your time",
|
|
355
373
|
"agent.calendarUnreadable": "None of this agent's schedules can be read as a cron expression, so nothing is planned.",
|
|
356
374
|
"agent.log": "Runs",
|
|
357
375
|
"agent.logEmpty": "This agent has not run yet.",
|
|
@@ -379,6 +397,10 @@
|
|
|
379
397
|
"settings.appearance.dark": "Dark",
|
|
380
398
|
"settings.timezone": "Timezone",
|
|
381
399
|
"settings.timezoneHint": "Used to read times, and suggested when you give an agent a schedule. A schedule keeps the timezone it was given, so travelling does not move the runs you already set.",
|
|
400
|
+
"settings.timezoneSearch": "Search city, zone or offset…",
|
|
401
|
+
"settings.timezoneNoMatch": "No timezone matches.",
|
|
402
|
+
"settings.timezoneOwn": "Your device",
|
|
403
|
+
"settings.timezoneAll": "All timezones",
|
|
382
404
|
"auth.signOut": "Sign out",
|
|
383
405
|
"auth.signOutFailed": "Signing out failed. Check your connection and try again.",
|
|
384
406
|
"signIn.refusedTitle": "Signed in, but not accepted",
|
package/src/i18n/es.json
CHANGED
|
@@ -282,12 +282,13 @@
|
|
|
282
282
|
"agent.tab.chat": "Chat",
|
|
283
283
|
"agent.tab.calendar": "Calendario",
|
|
284
284
|
"agent.tab.log": "Registro",
|
|
285
|
-
"agent.
|
|
286
|
-
"agent.
|
|
287
|
-
"agent.
|
|
288
|
-
"agent.
|
|
285
|
+
"agent.status.running": "En marcha",
|
|
286
|
+
"agent.status.waiting": "Esperando su próxima ejecución",
|
|
287
|
+
"agent.status.failed": "La última ejecución falló",
|
|
288
|
+
"agent.status.paused": "En pausa",
|
|
289
|
+
"agent.status.unknown": "Entorno de ejecución no accesible",
|
|
289
290
|
"agent.nextRun": "próxima ejecución {when} ({zone})",
|
|
290
|
-
"agent.
|
|
291
|
+
"agent.lastRun": "última ejecución {when}",
|
|
291
292
|
"agent.pausedNoRuns": "no se dispara ningún horario, no se acepta ninguna tarea ni ningún correo; el chat sigue respondiendo",
|
|
292
293
|
"agent.pause": "Pausar",
|
|
293
294
|
"agent.resume": "Reanudar",
|
|
@@ -296,6 +297,7 @@
|
|
|
296
297
|
"agent.runNowPaused": "Reanuda el agente antes de ejecutarlo.",
|
|
297
298
|
"agent.actionFailed": "Eso no ha funcionado. Inténtalo de nuevo.",
|
|
298
299
|
"agent.keyMissing": "Este agente no tiene clave de aplicación, así que no puede empezar ninguna ejecución. Sustituye su clave en «Identidad», dentro del perfil.",
|
|
300
|
+
"agent.createdBy": "por {name}",
|
|
299
301
|
"agent.notPermitted": "Puedes leer este agente, pero no manejarlo. Manejar un agente necesita el permiso «ejecutar agentes».",
|
|
300
302
|
"agent.noRuntime": "Esta instalación no tiene entorno de ejecución de agentes, así que este agente no puede chatear ni ejecutarse aquí. Su definición se conserva y vuelve a funcionar en una instalación que sí lo tenga.",
|
|
301
303
|
"agent.loadFailed": "Este agente no se ha podido cargar. Comprueba tu acceso e inténtalo de nuevo.",
|
|
@@ -320,10 +322,10 @@
|
|
|
320
322
|
"agent.contactAdd": "Añadir un contacto",
|
|
321
323
|
"agent.contactAddReason": "Una dirección de correo pertenece a la instalación, no a la definición del agente, así que se configura donde está desplegado el entorno de ejecución.",
|
|
322
324
|
"agent.knows": "Lo que sabe",
|
|
323
|
-
"agent.knowsHint": "
|
|
324
|
-
"agent.addReference": "Añadir
|
|
325
|
+
"agent.knowsHint": "Lo que lee este agente y para qué cuenta cada entrada. Un mensaje de sistema lo instruye — una carpeta, un documento o una tabla. El contexto semántico se busca y en la memoria se escribe de vuelta, por eso ambos son carpetas.",
|
|
326
|
+
"agent.addReference": "Añadir algo para leer",
|
|
325
327
|
"agent.removeReferenceOf": "Dejar de leer {title}",
|
|
326
|
-
"agent.
|
|
328
|
+
"agent.pickEntry": "Qué se lee",
|
|
327
329
|
"agent.roleFor": "Para qué cuenta {title}",
|
|
328
330
|
"agent.role.system-message": "Mensaje de sistema",
|
|
329
331
|
"agent.role.semantic-context": "Contexto semántico",
|
|
@@ -335,11 +337,12 @@
|
|
|
335
337
|
"agent.removeToolServerOf": "Quitarle {name} a este agente",
|
|
336
338
|
"agent.toolServerUnavailable": "Ya no alcanzas este servidor",
|
|
337
339
|
"agent.toolsDelegationNotice": "Quien pueda ejecutar este agente actúa sobre tu conexión.",
|
|
340
|
+
"agent.toolsDelegationNoticeBy": "Quien pueda ejecutar este agente actúa sobre la conexión de {name}.",
|
|
341
|
+
"agent.toolsDelegationNoticeSomebody": "Quien pueda ejecutar este agente actúa sobre la conexión de quien le dio estas herramientas, no sobre la tuya.",
|
|
338
342
|
"agent.toolsNotConnected": "Todavía no has iniciado sesión en el portal de la empresa, así que no hay nada que ceder. Abre «Herramientas» para conectarte.",
|
|
339
343
|
"agent.toolsNoneLeft": "Todos los servidores que alcanzas ya están dados a este agente.",
|
|
340
344
|
"agent.schedule": "Horario",
|
|
341
345
|
"agent.scheduleHint": "Cuándo actúa este agente por su cuenta y qué ejecuta. Cada horario conserva la zona horaria con la que se creó, así que significa la misma hora para todo el que lo lea.",
|
|
342
|
-
"agent.scheduleEmpty": "Este agente solo se ejecuta cuando se le pide.",
|
|
343
346
|
"agent.addSchedule": "Añadir un horario",
|
|
344
347
|
"agent.removeSchedule": "Quitar este horario",
|
|
345
348
|
"agent.scheduleTarget": "Qué ejecutar",
|
|
@@ -350,8 +353,23 @@
|
|
|
350
353
|
"agent.model": "Modelo",
|
|
351
354
|
"agent.modelHint": "Qué modelo piensa. El entorno de ejecución recoge un cambio en menos de un minuto.",
|
|
352
355
|
"agent.modelFigures": "{context} de contexto · {input} $ de entrada / {output} $ de salida por cada millón de tokens",
|
|
356
|
+
"agent.usage": "{input} de entrada · {output} de salida · {cacheRead} leídos de caché · {cacheWrite} escritos en caché",
|
|
357
|
+
"agent.usageUnknown": "No hay cifras de consumo para esta ejecución",
|
|
358
|
+
"agent.usageNotReported": "Este proveedor no informa del consumo",
|
|
359
|
+
"agent.costMissing": "El coste no figura en el registro de la pasarela",
|
|
360
|
+
"agent.costNotConfigured": "Sin cifras de coste: esta instalación no tiene un token de lectura de AI Gateway.",
|
|
361
|
+
"agent.costUnreadable": "Sin cifras de coste: no se pudo leer el registro de AI Gateway.",
|
|
362
|
+
"agent.costPartial": "El registro de la pasarela se leyó hasta su límite de páginas, así que estos totales son un mínimo.",
|
|
363
|
+
"agent.costWindow": "Últimos {days} días",
|
|
364
|
+
"agent.costWindowCalls": "{count} llamadas al modelo",
|
|
365
|
+
"agent.costs": "Coste",
|
|
366
|
+
"agent.costsSpent": "{amount} en los últimos {days} días",
|
|
367
|
+
"agent.costsSpentPast": "Ya gastado, en ejecuciones pasadas: no es una previsión para el modelo elegido arriba.",
|
|
368
|
+
"agent.costsSpentWith": "con {models}",
|
|
369
|
+
"agent.modelFiguresStale": "Estas cifras proceden de una tabla incorporada y pueden estar desactualizadas.",
|
|
353
370
|
"agent.calendar": "Próximas ejecuciones",
|
|
354
|
-
"agent.calendarZones": "Cada hora se muestra en la zona horaria de su propio horario, porque es cuando se ejecuta el agente.",
|
|
371
|
+
"agent.calendarZones": "Cada hora se muestra en la zona horaria de su propio horario, porque es cuando se ejecuta el agente. Si tu propia zona horaria difiere, se añade al lado.",
|
|
372
|
+
"agent.calendarYourTime": "{when} en tu hora",
|
|
355
373
|
"agent.calendarUnreadable": "Ninguno de los horarios de este agente se puede leer como expresión cron, así que no hay nada planificado.",
|
|
356
374
|
"agent.log": "Ejecuciones",
|
|
357
375
|
"agent.logEmpty": "Este agente todavía no se ha ejecutado.",
|
|
@@ -379,6 +397,10 @@
|
|
|
379
397
|
"settings.appearance.dark": "Oscuro",
|
|
380
398
|
"settings.timezone": "Zona horaria",
|
|
381
399
|
"settings.timezoneHint": "Se usa para leer las horas y se propone cuando le das un horario a un agente. Un horario conserva la zona con la que se creó, así que viajar no mueve las ejecuciones que ya has fijado.",
|
|
400
|
+
"settings.timezoneSearch": "Buscar ciudad, zona o desfase…",
|
|
401
|
+
"settings.timezoneNoMatch": "Ninguna zona horaria coincide.",
|
|
402
|
+
"settings.timezoneOwn": "Tu dispositivo",
|
|
403
|
+
"settings.timezoneAll": "Todas las zonas horarias",
|
|
382
404
|
"auth.signOut": "Cerrar sesión",
|
|
383
405
|
"auth.signOutFailed": "El cierre de sesión ha fallado. Comprueba tu conexión e inténtalo de nuevo.",
|
|
384
406
|
"signIn.refusedTitle": "Sesión iniciada, pero no aceptada",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Info } from "lucide-react";
|
|
2
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An explanation behind an ⓘ, beside the thing it explains (#204, shared since #249).
|
|
6
|
+
*
|
|
7
|
+
* It exists for the sentence that answers "what IS this" — read once, then furniture. Not for the
|
|
8
|
+
* sentence you need WHILE you type: `agent.cronHint` ("five fields: minute, hour, …") deliberately
|
|
9
|
+
* stays a visible paragraph, because a reference you read while writing must not be behind a hover.
|
|
10
|
+
* That is the dividing line: *what is this* may hide, *how do I write this* may not.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ The hint is the trigger's accessible NAME, not only the tooltip's content: Radix describes the
|
|
13
|
+
* trigger by the content only while it is open, and a listener who tabs past a nameless icon button
|
|
14
|
+
* would never learn there is anything behind it.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ A real `<button>`, not a bare icon or a `<span>` with a hover style. A tooltip that only opens
|
|
17
|
+
* on hover cannot be opened at all on a touch device, and an element that is not focusable is one
|
|
18
|
+
* the keyboard walks straight past.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ It does NOT replace a field's description. Where this sits beside a form control, the control
|
|
21
|
+
* keeps its own `aria-describedby` pointing at an `sr-only` copy of the same words — the tooltip's
|
|
22
|
+
* content is in the accessibility tree only while it is open, so a field that gave up its
|
|
23
|
+
* description for one would quietly have lost it (#249).
|
|
24
|
+
*/
|
|
25
|
+
export function SectionHint({ hint }: { hint: string }) {
|
|
26
|
+
return (
|
|
27
|
+
<TooltipProvider delayDuration={300}>
|
|
28
|
+
<Tooltip>
|
|
29
|
+
<TooltipTrigger
|
|
30
|
+
type="button"
|
|
31
|
+
aria-label={hint}
|
|
32
|
+
className="rounded-full text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
|
33
|
+
>
|
|
34
|
+
<Info aria-hidden="true" className="size-3.5" />
|
|
35
|
+
</TooltipTrigger>
|
|
36
|
+
<TooltipContent className="max-w-xs">{hint}</TooltipContent>
|
|
37
|
+
</Tooltip>
|
|
38
|
+
</TooltipProvider>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Check, ChevronsUpDown } from "lucide-react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
Command,
|
|
5
|
+
CommandEmpty,
|
|
6
|
+
CommandGroup,
|
|
7
|
+
CommandInput,
|
|
8
|
+
CommandItem,
|
|
9
|
+
CommandList,
|
|
10
|
+
} from "@/components/ui/command";
|
|
11
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
12
|
+
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
13
|
+
import { cn } from "@/lib/utils";
|
|
14
|
+
import {
|
|
15
|
+
browserTimezone,
|
|
16
|
+
type TimezoneOption,
|
|
17
|
+
timezoneOption,
|
|
18
|
+
timezoneOptions,
|
|
19
|
+
} from "../timezone.ts";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Picking one of 418 timezones.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ A `Select` was the wrong shape and this replaces it (#243). Four hundred entries cannot be
|
|
25
|
+
* scrolled to, and a bare zone name does not say whether you got the right one — `Europe/Berlin`
|
|
26
|
+
* and `Europe/Madrid` are the same clock, `America/New_York` is not. So: type to search, and the
|
|
27
|
+
* current offset stands in front of every name.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ The label is computed here, at render time, and never stored. A zone's offset AND its
|
|
30
|
+
* abbreviation move with daylight saving, so a written-down label is wrong for half of every year.
|
|
31
|
+
*/
|
|
32
|
+
interface TimezoneComboboxProps {
|
|
33
|
+
id: string;
|
|
34
|
+
value: string;
|
|
35
|
+
onChange(zone: string): void;
|
|
36
|
+
/** Handed in rather than read from the clock, so a test does not have to travel in time. */
|
|
37
|
+
now?: Date;
|
|
38
|
+
describedBy?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function OptionLabel({ option }: { option: TimezoneOption }) {
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
{/* Fixed width, so the zone names line up into a column instead of stepping left and right
|
|
45
|
+
with every offset. Tabular figures keep "GMT+05:30" and "GMT-04:00" the same width, and
|
|
46
|
+
⚠️ `whitespace-nowrap` is not decoration: the longest label ("GMT-09:00 · HADT") wrapped
|
|
47
|
+
onto a second line and pushed its own row out of the column. */}
|
|
48
|
+
<span className="w-36 shrink-0 whitespace-nowrap text-xs tabular-nums text-muted-foreground">
|
|
49
|
+
{option.offsetLabel}
|
|
50
|
+
{option.abbreviation ? ` · ${option.abbreviation}` : ""}
|
|
51
|
+
</span>
|
|
52
|
+
<span className="truncate">{option.zone.replace(/_/g, " ")}</span>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function TimezoneCombobox({ id, value, onChange, now, describedBy }: TimezoneComboboxProps) {
|
|
58
|
+
const i18n = useI18n();
|
|
59
|
+
const [open, setOpen] = useState(false);
|
|
60
|
+
const at = now ?? new Date();
|
|
61
|
+
// The clock is a dependency on purpose: without it in the key, a session that crossed a DST
|
|
62
|
+
// boundary would keep showing yesterday's offsets.
|
|
63
|
+
const at_ = at.getTime();
|
|
64
|
+
|
|
65
|
+
const options = useMemo(
|
|
66
|
+
() => timezoneOptions(value, new Date(at_), i18n.locale),
|
|
67
|
+
[value, at_, i18n.locale],
|
|
68
|
+
);
|
|
69
|
+
const selected = useMemo(
|
|
70
|
+
() => timezoneOption(value, new Date(at_), i18n.locale),
|
|
71
|
+
[value, at_, i18n.locale],
|
|
72
|
+
);
|
|
73
|
+
// The reader's own zone, pinned above the list: it is the answer in the overwhelming majority of
|
|
74
|
+
// cases, and finding it in a sorted list of 418 is the work this saves.
|
|
75
|
+
const own = useMemo(
|
|
76
|
+
() => timezoneOption(browserTimezone(), new Date(at_), i18n.locale),
|
|
77
|
+
[at_, i18n.locale],
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const choose = (zone: string) => {
|
|
81
|
+
onChange(zone);
|
|
82
|
+
setOpen(false);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
87
|
+
<PopoverTrigger asChild>
|
|
88
|
+
<button
|
|
89
|
+
id={id}
|
|
90
|
+
type="button"
|
|
91
|
+
role="combobox"
|
|
92
|
+
aria-expanded={open}
|
|
93
|
+
aria-describedby={describedBy}
|
|
94
|
+
className="flex w-full items-center gap-2 rounded-md border bg-background px-3 py-2 text-left text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
95
|
+
>
|
|
96
|
+
<OptionLabel option={selected} />
|
|
97
|
+
<ChevronsUpDown aria-hidden="true" className="ml-auto size-4 shrink-0 opacity-50" />
|
|
98
|
+
</button>
|
|
99
|
+
</PopoverTrigger>
|
|
100
|
+
<PopoverContent className="w-(--radix-popover-trigger-width) p-0" align="start">
|
|
101
|
+
{/* ⚠️ A substring filter, not cmdk's default fuzzy one. Fuzzy matches a scattered
|
|
102
|
+
subsequence, so over 418 zones "berlin" also surfaced Brisbane, Dublin and Lisbon —
|
|
103
|
+
technically a match, useless as an answer. Prefix beats contains, so typing a city puts
|
|
104
|
+
that city first. An empty search returns 1 for everything, which leaves the list in DOM
|
|
105
|
+
order: sorted west to east. */}
|
|
106
|
+
<Command
|
|
107
|
+
filter={(value, search, keywords) => {
|
|
108
|
+
const needle = search.trim().toLowerCase();
|
|
109
|
+
if (!needle) return 1;
|
|
110
|
+
const haystack = [value.toLowerCase(), ...(keywords ?? [])];
|
|
111
|
+
if (haystack.some((word) => word.startsWith(needle))) return 2;
|
|
112
|
+
return haystack.some((word) => word.includes(needle)) ? 1 : 0;
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<CommandInput placeholder={i18n.t("settings.timezoneSearch")} />
|
|
116
|
+
<CommandList>
|
|
117
|
+
<CommandEmpty>{i18n.t("settings.timezoneNoMatch")}</CommandEmpty>
|
|
118
|
+
{own.zone !== value ? (
|
|
119
|
+
<CommandGroup heading={i18n.t("settings.timezoneOwn")}>
|
|
120
|
+
<CommandItem value={own.zone} keywords={own.keywords} onSelect={choose}>
|
|
121
|
+
<OptionLabel option={own} />
|
|
122
|
+
</CommandItem>
|
|
123
|
+
</CommandGroup>
|
|
124
|
+
) : null}
|
|
125
|
+
<CommandGroup heading={i18n.t("settings.timezoneAll")}>
|
|
126
|
+
{options.map((option) => (
|
|
127
|
+
<CommandItem
|
|
128
|
+
key={option.zone}
|
|
129
|
+
value={option.zone}
|
|
130
|
+
keywords={option.keywords}
|
|
131
|
+
onSelect={choose}
|
|
132
|
+
>
|
|
133
|
+
<OptionLabel option={option} />
|
|
134
|
+
<Check
|
|
135
|
+
aria-hidden="true"
|
|
136
|
+
className={cn(
|
|
137
|
+
"ml-auto size-4 shrink-0",
|
|
138
|
+
option.zone === value ? "opacity-100" : "opacity-0",
|
|
139
|
+
)}
|
|
140
|
+
/>
|
|
141
|
+
</CommandItem>
|
|
142
|
+
))}
|
|
143
|
+
</CommandGroup>
|
|
144
|
+
</CommandList>
|
|
145
|
+
</Command>
|
|
146
|
+
</PopoverContent>
|
|
147
|
+
</Popover>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { createContext, type ReactNode, useContext, useMemo, useState } from "react";
|
|
2
|
-
import { browserTimezone,
|
|
2
|
+
import { browserTimezone, readChosenTimezone, rememberTimezone } from "./timezone.ts";
|
|
3
3
|
|
|
4
4
|
// The React seam of the timezone preference, the same shape as i18n-context and theme-context.
|
|
5
5
|
|
|
6
6
|
export interface TimezoneSelection {
|
|
7
|
+
/** The zone to read times in: the choice, or the browser's own where there is none. */
|
|
7
8
|
current: string;
|
|
9
|
+
/**
|
|
10
|
+
* ⚠️ The zone somebody actually CHOSE — `null` while they never did (#256). `current` cannot
|
|
11
|
+
* answer this: it falls back to the browser, and a screen that treated that as a preference would
|
|
12
|
+
* annotate every schedule with a guess nobody made.
|
|
13
|
+
*/
|
|
14
|
+
chosen: string | null;
|
|
8
15
|
select(zone: string): void;
|
|
9
16
|
}
|
|
10
17
|
|
|
@@ -16,17 +23,18 @@ interface TimezoneProviderProps {
|
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
export function TimezoneProvider({ store, children }: TimezoneProviderProps) {
|
|
19
|
-
const [
|
|
26
|
+
const [chosen, setChosen] = useState(() => readChosenTimezone(store));
|
|
20
27
|
|
|
21
28
|
const selection = useMemo<TimezoneSelection>(
|
|
22
29
|
() => ({
|
|
23
|
-
current,
|
|
30
|
+
current: chosen ?? browserTimezone(),
|
|
31
|
+
chosen,
|
|
24
32
|
select: (zone) => {
|
|
25
33
|
rememberTimezone(zone, store);
|
|
26
|
-
|
|
34
|
+
setChosen(zone);
|
|
27
35
|
},
|
|
28
36
|
}),
|
|
29
|
-
[
|
|
37
|
+
[chosen, store],
|
|
30
38
|
);
|
|
31
39
|
|
|
32
40
|
return <TimezoneContext value={selection}>{children}</TimezoneContext>;
|
|
@@ -43,3 +51,15 @@ export function useTimezone(): string {
|
|
|
43
51
|
const selection = useContext(TimezoneContext);
|
|
44
52
|
return selection?.current ?? browserTimezone();
|
|
45
53
|
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The zone the reader CHOSE, or `null` (#256).
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ Not `useTimezone`. This is for screens that add the reader's own reading BESIDE a time that is
|
|
59
|
+
* already correct — the agent's calendar. Nothing may be added for somebody who never set a zone:
|
|
60
|
+
* the browser's guess presented as "your time" is a setting the reader did not make, and it would
|
|
61
|
+
* put a second time on every row of a screen that was fine as it was.
|
|
62
|
+
*/
|
|
63
|
+
export function useChosenTimezone(): string | null {
|
|
64
|
+
return useContext(TimezoneContext)?.chosen ?? null;
|
|
65
|
+
}
|