From 535208dca57e6775d5605fbf58ffaf02d44ca515 Mon Sep 17 00:00:00 2001 From: Ryan Alyn Porter Date: Thu, 5 Jun 2025 22:15:27 -0400 Subject: [PATCH 1/3] Language localization, with Spanish support. --- .../app/{ => [locale]}/activity/page.tsx | 0 dashboard/app/{ => [locale]}/alerts/page.tsx | 0 dashboard/app/{ => [locale]}/batches/page.tsx | 0 .../app/{ => [locale]}/dashboard/layout.tsx | 0 .../app/{ => [locale]}/dashboard/page.tsx | 0 .../app/{ => [locale]}/datasets/page.tsx | 0 .../documentation/advanced/cli/page.tsx | 182 +++++ .../advanced/mcp-server/layout.tsx | 0 .../advanced/mcp-server/page.tsx | 509 +++++++++++++ .../[locale]/documentation/advanced/page.tsx | 164 ++++ .../documentation/advanced/sdk/page.tsx | 156 ++++ .../advanced/universal-code/page.tsx | 132 ++++ .../advanced/worker-nodes/page.tsx | 370 +++++++++ .../documentation/basics/evaluations/page.tsx | 0 .../documentation/components/breadcrumb.tsx | 0 .../documentation/components/doc-button.tsx | 0 .../components/documentation-layout.tsx | 203 +++-- .../documentation/components/toc.tsx | 0 .../concepts/evaluations/layout.tsx | 0 .../concepts/evaluations/page.tsx | 97 +++ .../documentation/concepts/items/page.tsx | 218 ++++++ .../[locale]/documentation/concepts/page.tsx | 296 ++++++++ .../documentation/concepts/reports/page.tsx | 102 ++- .../concepts/score-results/page.tsx | 99 ++- .../concepts/scorecards/page.tsx | 398 ++++++++++ .../documentation/concepts/scores/page.tsx | 101 ++- .../documentation/concepts/solutions/page.tsx | 0 .../documentation/concepts/sources/page.tsx | 114 +++ .../concepts/task-dispatch/page.tsx | 0 .../documentation/concepts/tasks/page.tsx | 97 +++ .../evaluation-metrics/examples-data.ts | 0 .../evaluation-metrics/examples/page.tsx | 71 +- .../gauges-with-context/page.tsx | 392 +++++++++- .../gauges/accuracy/page.tsx | 74 +- .../gauges/agreement/page.tsx | 156 +++- .../gauges/class-imbalance/page.tsx | 84 ++- .../gauges/class-number/page.tsx | 147 +++- .../gauges/precision/page.tsx | 126 +++- .../evaluation-metrics/gauges/recall/page.tsx | 126 +++- .../documentation/evaluation-metrics/page.tsx | 712 ++++++++++++++++++ .../{ => [locale]}/documentation/layout.tsx | 0 .../methods/add-edit-score/page.tsx | 536 +++++++++++++ .../methods/add-edit-scorecard/page.tsx | 438 +++++++++++ .../methods/add-edit-source/page.tsx | 308 ++++++++ .../methods/evaluate-score/page.tsx | 298 ++++++++ .../methods/monitor-tasks/page.tsx | 278 +++++++ .../[locale]/documentation/methods/page.tsx | 214 ++++++ .../methods/profile-source/page.tsx | 322 ++++++++ dashboard/app/[locale]/documentation/page.tsx | 241 ++++++ .../evaluations/[id]/__tests__/page.test.tsx | 0 .../evaluations/[id]/client-layout.tsx | 0 .../evaluations/[id]/layout.tsx | 0 .../{ => [locale]}/evaluations/[id]/page.tsx | 0 .../app/{ => [locale]}/evaluations/page.tsx | 0 .../{ => [locale]}/feedback-queues/page.tsx | 0 .../app/{ => [locale]}/feedback/page.tsx | 0 dashboard/app/{ => [locale]}/items/page.tsx | 0 .../app/{ => [locale]}/lab/README-metadata.md | 0 .../{ => [locale]}/lab/activity/layout.tsx | 0 .../app/{ => [locale]}/lab/activity/page.tsx | 0 .../app/{ => [locale]}/lab/alerts/page.tsx | 0 .../app/{ => [locale]}/lab/analysis/page.tsx | 0 .../lab/batches/[id]/layout.tsx | 0 .../{ => [locale]}/lab/batches/[id]/page.tsx | 0 .../app/{ => [locale]}/lab/batches/page.tsx | 0 .../app/{ => [locale]}/lab/data/page.tsx | 0 .../{ => [locale]}/lab/datasets/layout.tsx | 0 .../app/{ => [locale]}/lab/datasets/page.tsx | 0 .../lab/evaluations/[id]/layout.tsx | 0 .../lab/evaluations/[id]/page.tsx | 0 .../score-results/[scoreResultId]/layout.tsx | 0 .../score-results/[scoreResultId]/page.tsx | 0 .../evaluations/[id]/score-results/layout.tsx | 0 .../evaluations/[id]/score-results/page.tsx | 0 .../{ => [locale]}/lab/evaluations/layout.tsx | 0 .../{ => [locale]}/lab/evaluations/page.tsx | 0 .../lab/feedback-queues/page.tsx | 0 .../{ => [locale]}/lab/items/[id]/page.tsx | 0 .../app/{ => [locale]}/lab/items/page.tsx | 0 dashboard/app/{ => [locale]}/lab/layout.tsx | 0 .../{ => [locale]}/lab/metadata-template.txt | 0 .../{ => [locale]}/lab/reports/[id]/page.tsx | 0 .../lab/reports/edit/[id]/page.tsx | 0 .../{ => [locale]}/lab/reports/edit/page.tsx | 0 .../app/{ => [locale]}/lab/reports/page.tsx | 0 .../lab/scorecards/[id]/layout.tsx | 0 .../lab/scorecards/[id]/page.tsx | 0 .../[id]/scores/[scoreId]/layout.tsx | 0 .../scorecards/[id]/scores/[scoreId]/page.tsx | 0 .../lab/scorecards/[id]/scores/layout.tsx | 0 .../{ => [locale]}/lab/scorecards/layout.tsx | 0 .../{ => [locale]}/lab/scorecards/page.tsx | 0 .../lab/settings/account/page.tsx | 24 +- dashboard/app/[locale]/lab/settings/page.tsx | 53 ++ .../{ => [locale]}/lab/tasks/[id]/page.tsx | 0 .../app/{ => [locale]}/lab/tasks/layout.tsx | 0 dashboard/app/[locale]/layout.tsx | 82 ++ dashboard/app/{ => [locale]}/menu-items.ts | 0 dashboard/app/{ => [locale]}/page.module.css | 0 dashboard/app/[locale]/page.tsx | 281 +++++++ .../app/{ => [locale]}/platform/page.tsx | 0 .../reports/[id]/client-layout.tsx | 0 .../{ => [locale]}/reports/[id]/layout.tsx | 0 .../app/{ => [locale]}/reports/[id]/page.tsx | 0 .../app/{ => [locale]}/reports/layout.tsx | 0 dashboard/app/{ => [locale]}/reports/page.tsx | 0 .../scores/[scoreId]/edit/page.tsx | 0 .../app/{ => [locale]}/scorecards/page.tsx | 0 .../{ => [locale]}/settings/account/page.tsx | 0 .../app/{ => [locale]}/settings/page.tsx | 0 .../solutions/call-center-qa/page.tsx | 0 .../solutions/enterprise/page.tsx | 0 .../solutions/optimizer-agents/page.tsx | 0 .../solutions/platform/page.tsx | 0 .../solutions/resources/page.tsx | 0 dashboard/app/contexts/TranslationContext.tsx | 77 ++ .../advanced/mcp-server/page.tsx | 255 ------- dashboard/app/documentation/advanced/page.tsx | 86 --- .../app/documentation/advanced/sdk/page.tsx | 77 -- .../advanced/worker-nodes/page.tsx | 184 ----- .../app/documentation/concepts/items/page.tsx | 105 --- dashboard/app/documentation/concepts/page.tsx | 152 ---- .../concepts/scorecards/page.tsx | 203 ----- .../documentation/concepts/sources/page.tsx | 53 -- .../documentation/evaluation-metrics/page.tsx | 480 ------------ .../methods/add-edit-score/page.tsx | 264 ------- .../methods/add-edit-scorecard/page.tsx | 215 ------ .../methods/add-edit-source/page.tsx | 150 ---- .../methods/evaluate-score/page.tsx | 145 ---- .../methods/monitor-tasks/page.tsx | 135 ---- dashboard/app/documentation/methods/page.tsx | 111 --- .../methods/profile-source/page.tsx | 157 ---- dashboard/app/documentation/page.tsx | 118 --- dashboard/app/lab/settings/page.tsx | 32 - dashboard/app/layout.tsx | 70 +- dashboard/app/page.tsx | 286 +------ dashboard/components/ItemScoreResults.tsx | 10 +- dashboard/components/ScorecardContext.tsx | 10 +- dashboard/components/dashboard-layout.tsx | 4 +- .../components/evaluations-dashboard.tsx | 11 +- dashboard/components/items-dashboard.tsx | 63 +- dashboard/components/items/ItemCard.tsx | 29 +- .../components/items/ScoreResultCard.tsx | 19 +- dashboard/components/logo-square.tsx | 4 +- dashboard/components/reports-dashboard.tsx | 6 +- dashboard/components/scorecards-dashboard.tsx | 4 +- .../task-dispatch/RunReportButton.tsx | 6 +- .../task-dispatch/configs/evaluations.tsx | 12 +- .../task-dispatch/configs/reports.tsx | 8 +- dashboard/components/ui/language-selector.tsx | 73 ++ .../ui/public-language-selector.tsx | 47 ++ dashboard/components/ui/timestamp.tsx | 80 +- dashboard/i18n.ts | 16 + dashboard/messages/en.json | 339 +++++++++ dashboard/messages/es.json | 360 +++++++++ dashboard/middleware.ts | 28 + dashboard/package-lock.json | 130 +++- dashboard/package.json | 1 + dashboard/utils/format-time-i18n.ts | 66 ++ dashboard/utils/format-time.ts | 3 +- 160 files changed, 9369 insertions(+), 3516 deletions(-) rename dashboard/app/{ => [locale]}/activity/page.tsx (100%) rename dashboard/app/{ => [locale]}/alerts/page.tsx (100%) rename dashboard/app/{ => [locale]}/batches/page.tsx (100%) rename dashboard/app/{ => [locale]}/dashboard/layout.tsx (100%) rename dashboard/app/{ => [locale]}/dashboard/page.tsx (100%) rename dashboard/app/{ => [locale]}/datasets/page.tsx (100%) rename dashboard/app/{ => [locale]}/documentation/advanced/cli/page.tsx (73%) rename dashboard/app/{ => [locale]}/documentation/advanced/mcp-server/layout.tsx (100%) create mode 100644 dashboard/app/[locale]/documentation/advanced/mcp-server/page.tsx create mode 100644 dashboard/app/[locale]/documentation/advanced/page.tsx create mode 100644 dashboard/app/[locale]/documentation/advanced/sdk/page.tsx rename dashboard/app/{ => [locale]}/documentation/advanced/universal-code/page.tsx (61%) create mode 100644 dashboard/app/[locale]/documentation/advanced/worker-nodes/page.tsx rename dashboard/app/{ => [locale]}/documentation/basics/evaluations/page.tsx (100%) rename dashboard/app/{ => [locale]}/documentation/components/breadcrumb.tsx (100%) rename dashboard/app/{ => [locale]}/documentation/components/doc-button.tsx (100%) rename dashboard/app/{ => [locale]}/documentation/components/documentation-layout.tsx (58%) rename dashboard/app/{ => [locale]}/documentation/components/toc.tsx (100%) rename dashboard/app/{ => [locale]}/documentation/concepts/evaluations/layout.tsx (100%) rename dashboard/app/{ => [locale]}/documentation/concepts/evaluations/page.tsx (61%) create mode 100644 dashboard/app/[locale]/documentation/concepts/items/page.tsx create mode 100644 dashboard/app/[locale]/documentation/concepts/page.tsx rename dashboard/app/{ => [locale]}/documentation/concepts/reports/page.tsx (60%) rename dashboard/app/{ => [locale]}/documentation/concepts/score-results/page.tsx (64%) create mode 100644 dashboard/app/[locale]/documentation/concepts/scorecards/page.tsx rename dashboard/app/{ => [locale]}/documentation/concepts/scores/page.tsx (64%) rename dashboard/app/{ => [locale]}/documentation/concepts/solutions/page.tsx (100%) create mode 100644 dashboard/app/[locale]/documentation/concepts/sources/page.tsx rename dashboard/app/{ => [locale]}/documentation/concepts/task-dispatch/page.tsx (100%) rename dashboard/app/{ => [locale]}/documentation/concepts/tasks/page.tsx (67%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/examples-data.ts (100%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/examples/page.tsx (85%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/gauges-with-context/page.tsx (56%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/gauges/accuracy/page.tsx (78%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/gauges/agreement/page.tsx (50%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/gauges/class-imbalance/page.tsx (78%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/gauges/class-number/page.tsx (60%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/gauges/precision/page.tsx (54%) rename dashboard/app/{ => [locale]}/documentation/evaluation-metrics/gauges/recall/page.tsx (51%) create mode 100644 dashboard/app/[locale]/documentation/evaluation-metrics/page.tsx rename dashboard/app/{ => [locale]}/documentation/layout.tsx (100%) create mode 100644 dashboard/app/[locale]/documentation/methods/add-edit-score/page.tsx create mode 100644 dashboard/app/[locale]/documentation/methods/add-edit-scorecard/page.tsx create mode 100644 dashboard/app/[locale]/documentation/methods/add-edit-source/page.tsx create mode 100644 dashboard/app/[locale]/documentation/methods/evaluate-score/page.tsx create mode 100644 dashboard/app/[locale]/documentation/methods/monitor-tasks/page.tsx create mode 100644 dashboard/app/[locale]/documentation/methods/page.tsx create mode 100644 dashboard/app/[locale]/documentation/methods/profile-source/page.tsx create mode 100644 dashboard/app/[locale]/documentation/page.tsx rename dashboard/app/{ => [locale]}/evaluations/[id]/__tests__/page.test.tsx (100%) rename dashboard/app/{ => [locale]}/evaluations/[id]/client-layout.tsx (100%) rename dashboard/app/{ => [locale]}/evaluations/[id]/layout.tsx (100%) rename dashboard/app/{ => [locale]}/evaluations/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/evaluations/page.tsx (100%) rename dashboard/app/{ => [locale]}/feedback-queues/page.tsx (100%) rename dashboard/app/{ => [locale]}/feedback/page.tsx (100%) rename dashboard/app/{ => [locale]}/items/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/README-metadata.md (100%) rename dashboard/app/{ => [locale]}/lab/activity/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/activity/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/alerts/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/analysis/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/batches/[id]/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/batches/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/batches/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/data/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/datasets/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/datasets/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/[id]/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/[id]/score-results/[scoreResultId]/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/[id]/score-results/[scoreResultId]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/[id]/score-results/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/[id]/score-results/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/evaluations/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/feedback-queues/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/items/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/items/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/metadata-template.txt (100%) rename dashboard/app/{ => [locale]}/lab/reports/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/reports/edit/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/reports/edit/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/reports/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/scorecards/[id]/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/scorecards/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/scorecards/[id]/scores/[scoreId]/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/scorecards/[id]/scores/[scoreId]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/scorecards/[id]/scores/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/scorecards/layout.tsx (100%) rename dashboard/app/{ => [locale]}/lab/scorecards/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/settings/account/page.tsx (85%) create mode 100644 dashboard/app/[locale]/lab/settings/page.tsx rename dashboard/app/{ => [locale]}/lab/tasks/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/lab/tasks/layout.tsx (100%) create mode 100644 dashboard/app/[locale]/layout.tsx rename dashboard/app/{ => [locale]}/menu-items.ts (100%) rename dashboard/app/{ => [locale]}/page.module.css (100%) create mode 100644 dashboard/app/[locale]/page.tsx rename dashboard/app/{ => [locale]}/platform/page.tsx (100%) rename dashboard/app/{ => [locale]}/reports/[id]/client-layout.tsx (100%) rename dashboard/app/{ => [locale]}/reports/[id]/layout.tsx (100%) rename dashboard/app/{ => [locale]}/reports/[id]/page.tsx (100%) rename dashboard/app/{ => [locale]}/reports/layout.tsx (100%) rename dashboard/app/{ => [locale]}/reports/page.tsx (100%) rename dashboard/app/{ => [locale]}/scorecards/[scorecardId]/scores/[scoreId]/edit/page.tsx (100%) rename dashboard/app/{ => [locale]}/scorecards/page.tsx (100%) rename dashboard/app/{ => [locale]}/settings/account/page.tsx (100%) rename dashboard/app/{ => [locale]}/settings/page.tsx (100%) rename dashboard/app/{ => [locale]}/solutions/call-center-qa/page.tsx (100%) rename dashboard/app/{ => [locale]}/solutions/enterprise/page.tsx (100%) rename dashboard/app/{ => [locale]}/solutions/optimizer-agents/page.tsx (100%) rename dashboard/app/{ => [locale]}/solutions/platform/page.tsx (100%) rename dashboard/app/{ => [locale]}/solutions/resources/page.tsx (100%) create mode 100644 dashboard/app/contexts/TranslationContext.tsx delete mode 100644 dashboard/app/documentation/advanced/mcp-server/page.tsx delete mode 100644 dashboard/app/documentation/advanced/page.tsx delete mode 100644 dashboard/app/documentation/advanced/sdk/page.tsx delete mode 100644 dashboard/app/documentation/advanced/worker-nodes/page.tsx delete mode 100644 dashboard/app/documentation/concepts/items/page.tsx delete mode 100644 dashboard/app/documentation/concepts/page.tsx delete mode 100644 dashboard/app/documentation/concepts/scorecards/page.tsx delete mode 100644 dashboard/app/documentation/concepts/sources/page.tsx delete mode 100644 dashboard/app/documentation/evaluation-metrics/page.tsx delete mode 100644 dashboard/app/documentation/methods/add-edit-score/page.tsx delete mode 100644 dashboard/app/documentation/methods/add-edit-scorecard/page.tsx delete mode 100644 dashboard/app/documentation/methods/add-edit-source/page.tsx delete mode 100644 dashboard/app/documentation/methods/evaluate-score/page.tsx delete mode 100644 dashboard/app/documentation/methods/monitor-tasks/page.tsx delete mode 100644 dashboard/app/documentation/methods/page.tsx delete mode 100644 dashboard/app/documentation/methods/profile-source/page.tsx delete mode 100644 dashboard/app/documentation/page.tsx delete mode 100644 dashboard/app/lab/settings/page.tsx create mode 100644 dashboard/components/ui/language-selector.tsx create mode 100644 dashboard/components/ui/public-language-selector.tsx create mode 100644 dashboard/i18n.ts create mode 100644 dashboard/messages/en.json create mode 100644 dashboard/messages/es.json create mode 100644 dashboard/middleware.ts create mode 100644 dashboard/utils/format-time-i18n.ts diff --git a/dashboard/app/activity/page.tsx b/dashboard/app/[locale]/activity/page.tsx similarity index 100% rename from dashboard/app/activity/page.tsx rename to dashboard/app/[locale]/activity/page.tsx diff --git a/dashboard/app/alerts/page.tsx b/dashboard/app/[locale]/alerts/page.tsx similarity index 100% rename from dashboard/app/alerts/page.tsx rename to dashboard/app/[locale]/alerts/page.tsx diff --git a/dashboard/app/batches/page.tsx b/dashboard/app/[locale]/batches/page.tsx similarity index 100% rename from dashboard/app/batches/page.tsx rename to dashboard/app/[locale]/batches/page.tsx diff --git a/dashboard/app/dashboard/layout.tsx b/dashboard/app/[locale]/dashboard/layout.tsx similarity index 100% rename from dashboard/app/dashboard/layout.tsx rename to dashboard/app/[locale]/dashboard/layout.tsx diff --git a/dashboard/app/dashboard/page.tsx b/dashboard/app/[locale]/dashboard/page.tsx similarity index 100% rename from dashboard/app/dashboard/page.tsx rename to dashboard/app/[locale]/dashboard/page.tsx diff --git a/dashboard/app/datasets/page.tsx b/dashboard/app/[locale]/datasets/page.tsx similarity index 100% rename from dashboard/app/datasets/page.tsx rename to dashboard/app/[locale]/datasets/page.tsx diff --git a/dashboard/app/documentation/advanced/cli/page.tsx b/dashboard/app/[locale]/documentation/advanced/cli/page.tsx similarity index 73% rename from dashboard/app/documentation/advanced/cli/page.tsx rename to dashboard/app/[locale]/documentation/advanced/cli/page.tsx index babde36ed..cd3e21bce 100644 --- a/dashboard/app/documentation/advanced/cli/page.tsx +++ b/dashboard/app/[locale]/documentation/advanced/cli/page.tsx @@ -1,6 +1,188 @@ 'use client'; +import { useTranslationContext } from '@/app/contexts/TranslationContext' + export default function CliPage() { + const { locale } = useTranslationContext(); + + if (locale === 'es') { + return ( +
+ + +

+ Herramienta CLI plexus +

+

+ Domina la interfaz de línea de comandos para gestionar tu implementación de Plexus. +

+ +
+
+

Resumen

+

+ La herramienta CLI de Plexus proporciona una poderosa interfaz de línea de comandos para gestionar tu implementación de Plexus, + con enfoque en evaluar y monitorear el rendimiento de cuadros de puntuación. +

+
+ +
+

Instalación

+

+ Instala la herramienta CLI de Plexus usando pip: +

+
+              
+ pip install plexus-cli +
+
+
+ +
+

Sistema de Identificadores Flexible

+

+ La CLI de Plexus usa un sistema de identificadores flexible que te permite referenciar recursos usando diferentes tipos de identificadores. Esto hace que los comandos sean más intuitivos y reduce la necesidad de buscar IDs específicos. +

+ +
+
+

Identificadores de Cuadros de Puntuación

+

+ Al usar el parámetro --scorecard, puedes proporcionar cualquiera de los siguientes: +

+
    +
  • ID de DynamoDB: El identificador único de la base de datos (ej., e51cd5ec-1940-4d8e-abcc-faa851390112)
  • +
  • Nombre: El nombre legible para humanos (ej., "Aseguramiento de Calidad")
  • +
  • Clave: La clave amigable para URLs (ej., aseguramiento-calidad)
  • +
  • ID Externo: Tu identificador externo personalizado (ej., qa-2023)
  • +
+
+ +
+

Identificadores de Puntuaciones

+

+ Similar a los cuadros de puntuación, las puntuaciones pueden referenciarse usando varios identificadores: +

+ +
    +
  • ID de DynamoDB: El UUID único asignado a la puntuación
  • +
  • Nombre: El nombre legible para humanos de la puntuación
  • +
  • Clave: La clave amigable para máquinas de la puntuación
  • +
  • ID Externo: Un identificador externo opcional para la puntuación
  • +
+
+ +
+

Identificadores de Cuenta

+

+ Al usar el parámetro --account, puedes proporcionar cualquiera de los siguientes: +

+
    +
  • ID de DynamoDB: El identificador único de la base de datos
  • +
  • Nombre: El nombre legible para humanos
  • +
  • Clave: La clave amigable para URLs
  • +
+
+
+
+ +
+

Comandos Comunes de Cuadros de Puntuación

+

+ Aquí tienes algunos comandos comunes para gestionar cuadros de puntuación: +

+ +
+              
+ {`# Listar todos los cuadros de puntuación +plexus scorecards list + +# Obtener información detallada sobre un cuadro específico +plexus scorecards info --scorecard ejemplo1 + +# Listar todas las puntuaciones en un cuadro +plexus scores list --scorecard ejemplo1 + +# Extraer configuración del cuadro a YAML +plexus scorecards pull --scorecard ejemplo1 --output ./mis-cuadros + +# Subir configuración del cuadro desde YAML +plexus scorecards push --scorecard ejemplo1 --file ./mi-cuadro.yaml --note "Configuración actualizada" + +# Eliminar un cuadro +plexus scorecards delete --scorecard ejemplo1`} +
+
+
+ +
+

Ejecutar Evaluaciones

+

+ La forma principal de evaluar el rendimiento de tu cuadro es usando el comando evaluate accuracy: +

+ +
+              
+ {`plexus \\ + evaluate \\ + accuracy \\ + --scorecard "Leads Entrantes" \\ + --number-of-samples 100 \\ + --visualize`} +
+
+ +
+

--scorecard: Cuadro a evaluar (acepta ID, nombre, clave o ID externo)

+

--number-of-samples: Número de muestras a evaluar (recomendado: 100+)

+

--visualize: Generar visualizaciones de los resultados

+
+ +

+ Este comando evaluará tu cuadro contra muestras etiquetadas y proporcionará métricas detalladas de precisión, + incluyendo precisión, recuperación y matrices de confusión cuando la visualización esté habilitada. +

+
+ +
+

Recursos Adicionales

+

+ Para información más detallada sobre características específicas: +

+
    +
  • Visita nuestra Guía de Evaluaciones
  • +
  • Consulta la ayuda integrada con plexus --help
  • +
  • Obtén ayuda específica de comandos con plexus evaluate accuracy --help
  • +
+
+
+
+ ); + } return (
+ +

Usar el Servidor MCP de Plexus

+

+ Conecta asistentes de IA como Claude a tus datos y funcionalidad de Plexus usando el servidor del Protocolo de Contexto de Modelo (MCP). +

+ +
+
+

¿Qué es MCP?

+

+ El Protocolo de Contexto de Modelo (MCP) es un estándar abierto diseñado por Anthropic que permite a los modelos de IA, como Claude, + interactuar de forma segura con herramientas y fuentes de datos externas. Para un asistente de IA, un servidor MCP actúa como una puerta de enlace, + permitiéndole acceder y usar capacidades de otros sistemas. En el contexto de Plexus, esto significa que puedes + empoderar a una IA para trabajar con tus cuadros de puntuación, evaluaciones y reportes directamente. Esto permite formas más dinámicas y + poderosas de interactuar con tu instancia de Plexus. + Para una inmersión más profunda en el protocolo mismo, consulta el anuncio oficial del Protocolo de Contexto de Modelo de Anthropic. +

+
+ +
+

Resumen del Servidor MCP de Plexus

+

+ El servidor MCP de Plexus es una herramienta pre-construida que puedes ejecutar en tu sistema. Una vez ejecutándose, permite a los asistentes de IA + que admiten MCP (como la aplicación de escritorio de Claude) conectarse a tu entorno de Plexus. Esta conexión permite a la IA + realizar varias acciones dentro de Plexus en tu nombre, como listar cuadros de puntuación, recuperar detalles de reportes, o + incluso iniciar nuevas evaluaciones. El servidor típicamente se ejecuta a través de un script wrapper (plexus_fastmcp_wrapper.py) + que maneja la configuración del entorno y asegura una comunicación fluida con el cliente de IA. +

+
+ +
+

Obtener el Código del Servidor

+

+ Para ejecutar el servidor MCP de Plexus, primero necesitarás obtener el código del servidor. Esto está disponible en el repositorio principal de GitHub de Plexus. + Puedes clonarlo o descargarlo desde: https://github.com/AnthusAI/Plexus. + Los scripts necesarios (plexus_fastmcp_wrapper.py y plexus_fastmcp_server.py) están típicamente ubicados en MCP/ dentro del repositorio. + Principalmente necesitarás estos archivos y asegurar que sus dependencias puedan cumplirse en tu entorno de Python. +

+
+ +
+

Configurar un Cliente MCP (ej., Aplicación de Escritorio de Claude)

+

+ Para usar el servidor MCP de Plexus, necesitas un cliente MCP. Por ejemplo, si estás usando la aplicación de escritorio de Claude, + la configurarías creando o editando un archivo mcp.json. Este archivo le dice a Claude (u otro cliente) + cómo encontrar y comunicarse con tu servidor MCP de Plexus en ejecución. +

+

+ Aquí hay una configuración de ejemplo para tu archivo mcp.json. Necesitarás reemplazar las rutas de marcador de posición + (/path/to/...) con las rutas reales relevantes a tu sistema y donde has clonado el repositorio de Plexus. +

+
+              
+{`{ + "mcpServers": { + "plexus-mcp-service": { + "command": "/path/to/your/conda/envs/py311/bin/python", + "args": [ + "/path/to/your/Plexus/MCP/plexus_fastmcp_wrapper.py", + "--host", "127.0.0.1", + "--port", "8002", + "--transport", "stdio", + "--env-file", "/path/to/your/Plexus/.env", + "--target-cwd", "/path/to/your/Plexus/" + ], + "env": { + "PYTHONUNBUFFERED": "1", + "PYTHONPATH": "/path/to/your/Plexus" + } + } + } +}`} +
+
+

Partes clave de esta configuración:

+
    +
  • command: La ruta completa al intérprete de Python dentro de tu entorno conda de Plexus (ej., py311).
  • +
  • args: Especifica el script wrapper a ejecutar (plexus_fastmcp_wrapper.py) y sus parámetros. + Los argumentos --host y --port configuran los ajustes del servidor local. + El argumento --transport stdio es estándar para comunicación cliente-servidor. + El argumento --env-file debe apuntar directamente a tu archivo .env (que contiene claves API). + El --target-cwd debe apuntar a tu directorio raíz del proyecto Plexus.
  • +
  • env.PYTHONPATH: Debe apuntar a la raíz de tu directorio del proyecto Plexus para asegurar que el servidor pueda encontrar todos los módulos de Python necesarios.
  • +
+

+ La ubicación del archivo mcp.json puede variar dependiendo del cliente. Para la aplicación de escritorio de Claude, consulta su documentación para la ubicación correcta (a menudo en un directorio de configuración dentro de tu perfil de usuario). +

+
+ +
+

Herramientas y Capacidades Disponibles

+

Una vez que el servidor MCP de Plexus esté ejecutándose (a través del script wrapper) y tu asistente de IA esté conectado, puedes instruir al asistente para usar las siguientes herramientas:

+ +
+

Gestión de Cuadros de Puntuación

+
    +
  • + list_plexus_scorecards: Pide a la IA que liste los cuadros de puntuación disponibles en tu Dashboard de Plexus. + Opcionalmente puedes decirle que filtre por un nombre/clave de cuenta, un nombre parcial de cuadro de puntuación, o una clave de cuadro de puntuación. Por ejemplo: "Lista los cuadros de puntuación de Plexus para la cuenta 'Ventas' que incluyan 'Q3' en el nombre." +
  • +
  • + get_plexus_scorecard_info: Solicita información detallada sobre un cuadro de puntuación específico. + Proporciona a la IA un identificador para el cuadro de puntuación (como su nombre, clave, o ID). Devolverá la descripción del cuadro de puntuación, secciones, y las puntuaciones dentro de cada sección. Por ejemplo: "Obtén información para el cuadro de puntuación 'Satisfacción del Cliente Q3'." +
  • +
  • + get_plexus_score_details: Obtén detalles específicos para una puntuación particular dentro de un cuadro de puntuación, incluyendo su configuración e historial de versiones. + Necesitarás especificar tanto el cuadro de puntuación como la puntuación. También puedes pedir una versión específica de la puntuación. Por ejemplo: "Muéstrame los detalles para la puntuación 'Capacidad de Respuesta' en el cuadro de puntuación 'Tickets de Soporte', especialmente su versión campeón." +
  • +
+
+ +
+

Herramientas de Evaluación

+
    +
  • + run_plexus_evaluation: Instruye a la IA para iniciar una nueva evaluación de cuadro de puntuación. + Necesitas proporcionar el nombre del cuadro de puntuación y opcionalmente un nombre de puntuación específico y el número de muestras. El servidor enviará esta tarea a tu backend de Plexus. Nota que el servidor MCP en sí no rastrea el progreso; monitorearías la evaluación en el Dashboard de Plexus como siempre. Por ejemplo: "Ejecuta una evaluación de Plexus para el cuadro de puntuación 'Calidad de Leads' usando 100 muestras." +
  • +
+
+ +
+

Herramientas de Reportes

+
    +
  • + list_plexus_reports: Pide una lista de reportes generados. Puedes filtrar por cuenta o por un ID de configuración de reporte específico si lo conoces. + La IA devolverá una lista mostrando nombres de reportes, IDs, y cuándo fueron creados. Por ejemplo: "Lista los últimos reportes de Plexus para la cuenta principal." +
  • +
  • + get_plexus_report_details: Recupera información detallada sobre un reporte específico proporcionando su ID. + Esto incluye los parámetros del reporte, salida, y cualquier bloque generado. Por ejemplo: "Obtén los detalles para el reporte de Plexus ID '123-abc-456'." +
  • +
  • + get_latest_plexus_report: Una forma conveniente de obtener los detalles del reporte generado más recientemente. + Opcionalmente puedes filtrar por cuenta o ID de configuración de reporte. Por ejemplo: "Muéstrame el último reporte generado desde la configuración 'Rendimiento Semanal'." +
  • +
  • + list_plexus_report_configurations: Obtén una lista de todas las configuraciones de reporte disponibles para una cuenta. + Esto es útil para saber qué reportes *puedes* generar. Por ejemplo: "¿Qué configuraciones de reporte están disponibles para la cuenta 'Marketing'?" +
  • +
+
+ +
+

Herramientas de Utilidad

+
    +
  • + think: Una herramienta de planificación usada internamente por la IA para estructurar el razonamiento antes de usar otras herramientas. + Esto ayuda a la IA a organizar su enfoque para tareas complejas que pueden requerir múltiples pasos o llamadas de herramientas. +
  • +
+
+
+ +
+

Requisitos de Entorno para Ejecutar el Servidor

+
+
+

Software

+
    +
  • Python 3.11 o más nuevo (requerido por la librería fastmcp que usa el servidor).
  • +
  • Una instalación existente de Plexus y acceso a sus credenciales del dashboard.
  • +
  • El paquete Python python-dotenv (usado por el servidor para cargar tus claves API desde el archivo .env).
  • +
+
+
+

Archivo .env con Credenciales de Plexus

+

+ El servidor necesita acceder a tu API de Plexus. Crea un archivo llamado .env. El parámetro --env-file en tu mcp.json debe apuntar directamente a este archivo. + Típicamente se ubica en tu directorio raíz del proyecto Plexus principal (ej., Plexus/.env). +

+

Variables Requeridas en .env:

+
    +
  • PLEXUS_API_URL: La URL del endpoint de API para tu instancia de Plexus.
  • +
  • PLEXUS_API_KEY: Tu clave API para autenticar con Plexus.
  • +
  • PLEXUS_DASHBOARD_URL: La URL principal de tu dashboard de Plexus (usada para generar enlaces).
  • +
+

Variables Opcionales en .env:

+
    +
  • PLEXUS_ACCOUNT_KEY: Si trabajas con múltiples cuentas, puedes establecer una clave de cuenta predeterminada aquí.
  • +
  • LOG_LEVEL: Puedes establecer esto a DEBUG, INFO, WARNING, o ERROR para controlar la verbosidad del registro del servidor.
  • +
+
+
+
+ +
+

Ejecutar el Servidor

+

+ Una vez que tengas el código y tu archivo .env esté configurado, debes ejecutar el servidor usando el script plexus_fastmcp_wrapper.py como se configura en tu archivo mcp.json. + El cliente MCP (ej., Aplicación de Escritorio de Claude) ejecutará el comando especificado en mcp.json cuando intente conectarse al "plexus-mcp-service". +

+

+ Típicamente no ejecutas el script plexus_fastmcp_wrapper.py manualmente desde la terminal para uso del cliente. En su lugar, asegúrate de que tu mcp.json esté configurado correctamente, y la aplicación cliente iniciará el proceso del servidor según sea necesario. +

+

+ Asegúrate de que tu entorno Python de Plexus (ej., conda activate py311) esté correctamente referenciado por la ruta completa a python en el campo command de tu mcp.json. + El script wrapper maneja el paso de las variables de entorno y rutas necesarias al plexus_fastmcp_server.py subyacente. +

+
+ +
+

Solución de Problemas Comunes

+
    +
  • Errores de Conexión: Verifica dos veces todas las rutas en tu archivo mcp.json (command, args, env.PYTHONPATH). Asegúrate de que apunten con precisión a tu ejecutable de Python, el script plexus_fastmcp_wrapper.py, tu archivo .env, y tu directorio del proyecto.
  • +
  • Errores de Autenticación: Verifica que la ruta --env-file en mcp.json apunte correctamente a tu archivo .env y que este archivo contenga el PLEXUS_API_URL y PLEXUS_API_KEY correctos.
  • +
+
+ +
+

Registros del Servidor

+

+ La configuración del servidor MCP de Plexus (a través de plexus_fastmcp_wrapper.py) dirige los registros operacionales y mensajes de error a stderr. + Los clientes MCP como la aplicación de escritorio de Claude típicamente capturan y muestran estos registros stderr, o los almacenan en un archivo de registro dedicado. +

+

+ Por ejemplo, Cursor a menudo almacena registros de interacción MCP en ~/Library/Logs/Claude/mcp.log en macOS. Monitorear este archivo es clave para diagnosticar problemas si el cliente no los muestra directamente. +

+
+
+
+ ); + } + return ( +
+ + +

Using the Plexus MCP Server

+

+ Connect AI assistants like Claude to your Plexus data and functionality using the Model Context Protocol (MCP) server. +

+ +
+
+

What is MCP?

+

+ The Model Context Protocol (MCP) is an open standard designed by Anthropic that allows AI models, such as Claude, + to securely interact with external tools and data sources. For an AI assistant, an MCP server acts as a gateway, + enabling it to access and use capabilities from other systems. In the context of Plexus, this means you can + empower an AI to work with your scorecards, evaluations, and reports directly. This allows for more dynamic and + powerful ways to interact with your Plexus instance. + For a deeper dive into the protocol itself, see the official Anthropic Model Context Protocol announcement. +

+
+ +
+

Plexus MCP Server Overview

+

+ The Plexus MCP server is a pre-built tool that you can run on your system. Once running, it allows AI assistants + that support MCP (like the Claude desktop app) to connect to your Plexus environment. This connection lets the AI + perform various actions within Plexus on your behalf, such as listing scorecards, retrieving report details, or + even initiating new evaluations. The server is typically run via a wrapper script (plexus_fastmcp_wrapper.py) + which handles environment setup and ensures smooth communication with the AI client. +

+
+ +
+

Getting the Server Code

+

+ To run the Plexus MCP server, you'll first need to obtain the server code. This is available in the main Plexus GitHub repository. + You can clone or download it from: https://github.com/AnthusAI/Plexus. + The necessary scripts (plexus_fastmcp_wrapper.py and plexus_fastmcp_server.py) are typically located at MCP/ within the repository. + You will primarily need these files and to ensure their dependencies can be met in your Python environment. +

+
+ +
+

Setting Up an MCP Client (e.g., Claude Desktop App)

+

+ To use the Plexus MCP server, you need an MCP client. For example, if you are using the Claude desktop application, + you would configure it by creating or editing an mcp.json file. This file tells Claude (or another client) + how to find and communicate with your running Plexus MCP server. +

+

+ Here is an example configuration for your mcp.json file. You will need to replace the placeholder paths + (/path/to/...) with the actual paths relevant to your system and where you have cloned the Plexus repository. +

+
+            
+{`{ + "mcpServers": { + "plexus-mcp-service": { + "command": "/path/to/your/conda/envs/py311/bin/python", + "args": [ + "/path/to/your/Plexus/MCP/plexus_fastmcp_wrapper.py", + "--host", "127.0.0.1", + "--port", "8002", + "--transport", "stdio", + "--env-file", "/path/to/your/Plexus/.env", + "--target-cwd", "/path/to/your/Plexus/" + ], + "env": { + "PYTHONUNBUFFERED": "1", + "PYTHONPATH": "/path/to/your/Plexus" + } + } + } +}`} +
+
+

Key parts of this configuration:

+
    +
  • command: The full path to the Python interpreter within your Plexus conda environment (e.g., py311).
  • +
  • args: Specifies the wrapper script to run (plexus_fastmcp_wrapper.py) and its parameters. + The --host and --port arguments configure the local server settings. + The --transport stdio argument is standard for client-server communication. + The --env-file argument must point directly to your .env file (which contains API keys). + The --target-cwd should point to your Plexus project root directory.
  • +
  • env.PYTHONPATH: Should point to the root of your Plexus project directory to ensure the server can find all necessary Python modules.
  • +
+

+ The location of the mcp.json file can vary depending on the client. For the Claude desktop app, consult its documentation for the correct location (often in a configuration directory within your user profile). +

+
+ +
+

Available Tools & Capabilities

+

Once the Plexus MCP server is running (via the wrapper script) and your AI assistant is connected, you can instruct the assistant to use the following tools:

+ +
+

Scorecard Management

+
    +
  • + list_plexus_scorecards: Ask the AI to list available scorecards in your Plexus Dashboard. + You can optionally tell it to filter by an account name/key, a partial scorecard name, or a scorecard key. For example: "List Plexus scorecards for the 'Sales' account that include 'Q3' in the name." +
  • +
  • + get_plexus_scorecard_info: Request detailed information about a specific scorecard. + Provide the AI with an identifier for the scorecard (like its name, key, or ID). It will return the scorecard's description, sections, and the scores within each section. For example: "Get info for the 'Customer Satisfaction Q3' scorecard." +
  • +
  • + get_plexus_score_details: Get specific details for a particular score within a scorecard, including its configuration and version history. + You'll need to specify both the scorecard and the score. You can also ask for a specific version of the score. For example: "Show me the details for the 'Responsiveness' score in the 'Support Tickets' scorecard, especially its champion version." +
  • +
+
+ +
+

Evaluation Tools

+
    +
  • + run_plexus_evaluation: Instruct the AI to start a new scorecard evaluation. + You need to provide the scorecard name and optionally a specific score name and the number of samples. The server will dispatch this task to your Plexus backend. Note that the MCP server itself doesn't track the progress; you would monitor the evaluation in the Plexus Dashboard as usual. For example: "Run a Plexus evaluation for the 'Lead Quality' scorecard using 100 samples." +
  • +
+
+ +
+

Reporting Tools

+
    +
  • + list_plexus_reports: Ask for a list of generated reports. You can filter by account or by a specific report configuration ID if you know it. + The AI will return a list showing report names, IDs, and when they were created. For example: "List the latest Plexus reports for the main account." +
  • +
  • + get_plexus_report_details: Retrieve detailed information about a specific report by providing its ID. + This includes the report's parameters, output, and any generated blocks. For example: "Get the details for Plexus report ID '123-abc-456'." +
  • +
  • + get_latest_plexus_report: A convenient way to get the details of the most recently generated report. + You can optionally filter by account or report configuration ID. For example: "Show me the latest report generated from the 'Weekly Performance' configuration." +
  • +
  • + list_plexus_report_configurations: Get a list of all available report configurations for an account. + This is useful for knowing what reports you *can* generate. For example: "What report configurations are available for the 'Marketing' account?" +
  • +
+
+ +
+

Utility Tools

+
    +
  • + think: A planning tool used internally by the AI to structure reasoning before using other tools. + This helps the AI organize its approach to complex tasks that may require multiple steps or tool calls. +
  • +
+
+
+ +
+

Environment Requirements for Running the Server

+
+
+

Software

+
    +
  • Python 3.11 or newer (required by the fastmcp library the server uses).
  • +
  • An existing Plexus installation and access to its dashboard credentials.
  • +
  • The python-dotenv Python package (used by the server to load your API keys from the .env file).
  • +
+
+
+

.env File with Plexus Credentials

+

+ The server needs to access your Plexus API. Create a file named .env. The --env-file parameter in your mcp.json should point directly to this file. + It's typically located in your main Plexus project root directory (e.g., Plexus/.env). +

+

Required Variables in .env:

+
    +
  • PLEXUS_API_URL: The API endpoint URL for your Plexus instance.
  • +
  • PLEXUS_API_KEY: Your API key for authenticating with Plexus.
  • +
  • PLEXUS_DASHBOARD_URL: The main URL of your Plexus dashboard (used for generating links).
  • +
+

Optional Variables in .env:

+
    +
  • PLEXUS_ACCOUNT_KEY: If you work with multiple accounts, you can set a default account key here.
  • +
  • LOG_LEVEL: You can set this to DEBUG, INFO, WARNING, or ERROR to control the server's logging verbosity.
  • +
+
+
+
+ +
+

Running the Server

+

+ Once you have the code and your .env file is set up, you should run the server using the plexus_fastmcp_wrapper.py script as configured in your mcp.json file. + The MCP client (e.g., Claude Desktop App) will execute the command specified in mcp.json when it attempts to connect to the "plexus-mcp-service". +

+

+ You typically don't run the plexus_fastmcp_wrapper.py script manually from the terminal for client use. Instead, ensure your mcp.json is correctly configured, and the client application will start the server process as needed. +

+

+ Make sure your Plexus Python environment (e.g., conda activate py311) is correctly referenced by the full path to python in the command field of your mcp.json. + The wrapper script handles passing the necessary environment variables and paths to the underlying plexus_fastmcp_server.py. +

+
+ +
+

Troubleshooting Common Issues

+
    +
  • Connection Errors: Double-check all paths in your mcp.json file (command, args, env.PYTHONPATH). Ensure they accurately point to your Python executable, the plexus_fastmcp_wrapper.py script, your .env file, and your project directory.
  • +
  • Authentication Errors: Verify that the --env-file path in mcp.json correctly points to your .env file and that this file contains the correct PLEXUS_API_URL and PLEXUS_API_KEY.
  • +
+
+ +
+

Server Logs

+

+ The Plexus MCP server setup (via plexus_fastmcp_wrapper.py) directs operational logs and error messages to stderr. + MCP clients like the Claude desktop app typically capture and display these stderr logs, or store them in a dedicated log file. +

+

+ For instance, Cursor often stores MCP interaction logs in ~/Library/Logs/Claude/mcp.log on macOS. Monitoring this file is key for diagnosing issues if the client doesn't display them directly. +

+
+
+
+ ) +} \ No newline at end of file diff --git a/dashboard/app/[locale]/documentation/advanced/page.tsx b/dashboard/app/[locale]/documentation/advanced/page.tsx new file mode 100644 index 000000000..e3e8c3b9a --- /dev/null +++ b/dashboard/app/[locale]/documentation/advanced/page.tsx @@ -0,0 +1,164 @@ +'use client'; + +import { Button as DocButton } from "@/components/ui/button" +import { useTranslationContext } from '@/app/contexts/TranslationContext' +import Link from "next/link" + +export default function AdvancedPage() { + const { locale } = useTranslationContext(); + + if (locale === 'es') { + return ( +
+

Herramientas y Conceptos Avanzados

+

+ Explora herramientas y conceptos avanzados que permiten una integración más profunda y personalización de Plexus + para usuarios técnicos y desarrolladores. +

+ +
+
+

Interfaz de Línea de Comandos

+
+

+ La herramienta CLI plexus proporciona acceso potente por línea de comandos a toda la funcionalidad de Plexus, + perfecta para automatización y flujos de trabajo avanzados. +

+ + Explorar Herramienta CLI + +
+
+ +
+

Infraestructura de Nodos de Trabajo

+
+

+ Aprende cómo configurar y gestionar nodos de trabajo de Plexus para procesar tareas de manera eficiente + en tu infraestructura. +

+ + Aprender sobre Nodos de Trabajo + +
+
+ +
+

SDK de Python

+
+

+ Integra Plexus directamente en tus aplicaciones Python con nuestro SDK integral, + habilitando acceso programático a todas las características de la plataforma. +

+ + Explorar Referencia SDK + +
+
+ +
+

Fragmentos de Código Universal

+
+

+ Aprende sobre el formato de código YAML universal de Plexus diseñado para comunicación perfecta + entre humanos, modelos de IA y otros sistemas. +

+ + Explorar Fragmentos de Código Universal + +
+
+ +
+

Servidor MCP de Plexus

+
+

+ Habilita agentes de IA y herramientas para interactuar con la funcionalidad de Plexus usando el Protocolo Cooperativo Multi-Agente (MCP). +

+ + Explorar Servidor MCP + +
+
+
+
+ ); + } + + // English content (default) + return ( +
+

Advanced Tools & Concepts

+

+ Explore advanced tools and concepts that enable deeper integration and customization of Plexus + for technical users and developers. +

+ +
+
+

Command Line Interface

+
+

+ The plexus CLI tool provides powerful command-line access to all Plexus functionality, + perfect for automation and advanced workflows. +

+ + Explore CLI Tool + +
+
+ +
+

Worker Infrastructure

+
+

+ Learn how to set up and manage Plexus worker nodes to process tasks efficiently + across your infrastructure. +

+ + Learn About Workers + +
+
+ +
+

Python SDK

+
+

+ Integrate Plexus directly into your Python applications with our comprehensive SDK, + enabling programmatic access to all platform features. +

+ + Browse SDK Reference + +
+
+ +
+

Universal Code Snippets

+
+

+ Learn about Plexus's universal YAML code format designed for seamless communication + between humans, AI models, and other systems. +

+ + Explore Universal Code Snippets + +
+
+ +
+

Plexus MCP Server

+
+

+ Enable AI agents and tools to interact with Plexus functionality using the Multi-Agent Cooperative Protocol (MCP). +

+ + Explore MCP Server + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/dashboard/app/[locale]/documentation/advanced/sdk/page.tsx b/dashboard/app/[locale]/documentation/advanced/sdk/page.tsx new file mode 100644 index 000000000..f17fe2d69 --- /dev/null +++ b/dashboard/app/[locale]/documentation/advanced/sdk/page.tsx @@ -0,0 +1,156 @@ +'use client'; + +import { useTranslationContext } from '@/app/contexts/TranslationContext' + +export default function SdkPage() { + const { locale } = useTranslationContext(); + + if (locale === 'es') { + return ( +
+

Referencia del SDK de Python

+

+ Explora el SDK de Python para acceso programático a la funcionalidad de Plexus. +

+ +
+
+

Resumen

+

+ El SDK de Python de Plexus proporciona una forma simple e intuitiva de interactuar con Plexus + programáticamente. Úsalo para automatizar flujos de trabajo, gestionar recursos, e integrar + Plexus en tus aplicaciones. +

+
+ +
+

Instalación

+

+ Instala el SDK de Plexus usando pip: +

+
+              pip install plexus-sdk
+            
+
+ +
+

Inicio Rápido

+

+ Aquí tienes un ejemplo simple para comenzar: +

+
+              {`from plexus import Plexus
+
+# Inicializar el cliente
+plexus = Plexus(api_key="tu-api-key")
+
+# Crear una nueva fuente
+source = plexus.sources.create(
+    name="Mi Fuente",
+    type="text",
+    data="Contenido de ejemplo"
+)
+
+# Ejecutar una evaluación
+evaluation = plexus.evaluations.create(
+    source_id=source.id,
+    scorecard_id="tu-scorecard-id"
+)`}
+            
+
+ +
+

Documentación Completa

+

+ Para la referencia completa de la API, guías de autenticación, ejemplos de uso avanzado y mejores prácticas, + visita nuestra documentación integral del SDK de Python: +

+
+ + Ver Documentación Completa del SDK → + +
+
+
+
+ ); + } + return ( +
+

Python SDK Reference

+

+ Explore the Python SDK for programmatic access to Plexus functionality. +

+ +
+
+

Overview

+

+ The Plexus Python SDK provides a simple and intuitive way to interact with Plexus + programmatically. Use it to automate workflows, manage resources, and integrate + Plexus into your applications. +

+
+ +
+

Installation

+

+ Install the Plexus SDK using pip: +

+
+            pip install plexus-sdk
+          
+
+ +
+

Quick Start

+

+ Here's a simple example to get you started: +

+
+            {`from plexus import Plexus
+
+# Initialize the client
+plexus = Plexus(api_key="your-api-key")
+
+# Create a new source
+source = plexus.sources.create(
+    name="My Source",
+    type="text",
+    data="Sample content"
+)
+
+# Run an evaluation
+evaluation = plexus.evaluations.create(
+    source_id=source.id,
+    scorecard_id="your-scorecard-id"
+)`}
+          
+
+ +
+

Complete Documentation

+

+ For complete API reference, authentication guides, advanced usage examples, and best practices, + visit our comprehensive Python SDK documentation: +

+
+ + View Full SDK Documentation → + +
+
+
+
+ ) +} \ No newline at end of file diff --git a/dashboard/app/documentation/advanced/universal-code/page.tsx b/dashboard/app/[locale]/documentation/advanced/universal-code/page.tsx similarity index 61% rename from dashboard/app/documentation/advanced/universal-code/page.tsx rename to dashboard/app/[locale]/documentation/advanced/universal-code/page.tsx index dde85b2b9..63701644c 100644 --- a/dashboard/app/documentation/advanced/universal-code/page.tsx +++ b/dashboard/app/[locale]/documentation/advanced/universal-code/page.tsx @@ -3,8 +3,10 @@ import { MessageSquareCode } from 'lucide-react'; import { CodeSnippet } from '@/components/ui/code-snippet'; import FeedbackAnalysis from '@/components/blocks/FeedbackAnalysis'; +import { useTranslationContext } from '@/app/contexts/TranslationContext' export default function YAMLCodeStandardPage() { + const { locale } = useTranslationContext(); // Create the YAML data but also parse it into the object structure for the component const sampleYAMLCode = `# Sales Lead Routing Analysis Report Output # @@ -186,6 +188,136 @@ scores: indexed_items_file: "lead_routing_analysis_55125_items.json"`; + if (locale === 'es') { + return ( +
+
+

Fragmentos de Código Universal

+

+ Interfaz de código universal para humanos, modelos de IA y sistemas +

+
+ +
+
+

El Icono de Código Universal

+
+ +
+
+

+ En todo Plexus, este icono significa que puedes obtener datos estructurados que funcionan en cualquier lugar. Haz clic, copia la salida, + y pégala directamente en ChatGPT, Claude, tu editor de código, o compártela con otros miembros del equipo. + El formato YAML incluye contexto integrado para que cualquiera (humano o IA) entienda inmediatamente lo que está viendo. +

+

+ No más luchar con JSON denso o perder contexto cuando mueves datos entre herramientas. + Simplemente funciona, en cualquier lugar. +

+
+
+ +
+

Reporte Visual → Código Universal

+

+ Así es como funciona: cada reporte gráfico en Plexus tiene una representación de código correspondiente. + A continuación hay un análisis real de enrutamiento de leads de ventas. El reporte visual muestra puntuaciones de acuerdo, matrices de confusión e insights de manera hermosa. + El botón de Código revela los mismos datos como YAML contextual que funciona en cualquier lugar. +

+ +
+

Prueba el Botón de Código

+

+ Usa el botón de Código en la esquina superior derecha para ver cómo los insights visuales se transforman en YAML estructurado que funciona con cualquier herramienta de IA, sistema de documentación o repositorio de código. +

+
+ +
+
+
+
Código Universal
+
+
+
+
+ +
+
+ +
+

+ 💡 Prueba esto: Usa el botón de Código para revelar YAML contextual con comentarios explicativos. + Haz clic en el botón Copiar para copiar el código a tu portapapeles. + Pégalo en ChatGPT o Claude y pregunta: "¿Qué puntuaciones de enrutamiento de leads de ventas muestran el mayor desacuerdo entre revisores?" o "¿Qué recomendaciones de entrenamiento mejorarían la confiabilidad del enrutamiento de leads?" + La IA entenderá inmediatamente el contexto y te dará recomendaciones estratégicas. +

+
+
+ +
+

Disponible en Todas Partes

+

+ Cada bloque de reporte en Plexus genera automáticamente Fragmentos de Código Universal. Ya sea que estés trabajando con + análisis de temas, análisis de retroalimentación, matrices de confusión, o cualquier otra salida analítica, el distintivo + icono de código te da acceso instantáneo a datos estructurados y contextuales. +

+ +

+ También encontrarás Fragmentos de Código Universal en: +

+ +
+
+

📊 Bloques de Reporte

+

+ Cada salida analítica incluye el Icono de Código Universal para acceso instantáneo a datos +

+
+
+

🎯 Evaluaciones

+

+ Resultados de evaluación con matrices de confusión, métricas de precisión y datos de rendimiento +

+
+
+

📈 Analíticas

+

+ Análisis estadístico, puntuaciones de acuerdo e insights de rendimiento +

+
+
+

🔧 Configuraciones

+

+ Configuraciones de cuadros de puntuación y puntuaciones exportadas en formato universal +

+
+
+
+ +
+

Por Qué Esto Importa

+

+ Las exportaciones de datos tradicionales carecen de contexto cuando las mueves. + Los Fragmentos de Código Universal resuelven esto empaquetando tus datos con explicaciones integradas que viajan con ellos. +

+

+ Esto significa que puedes mover insights sin problemas entre Plexus, tus herramientas de IA, documentación, repositorios de código, + y conversaciones de equipo sin perder significado o requerir explicación adicional. +

+
+
+
+ ); + } + return (
diff --git a/dashboard/app/[locale]/documentation/advanced/worker-nodes/page.tsx b/dashboard/app/[locale]/documentation/advanced/worker-nodes/page.tsx new file mode 100644 index 000000000..4df01e6a9 --- /dev/null +++ b/dashboard/app/[locale]/documentation/advanced/worker-nodes/page.tsx @@ -0,0 +1,370 @@ +'use client'; + +import { useTranslationContext } from '@/app/contexts/TranslationContext' + +export default function WorkerNodesPage() { + const { locale } = useTranslationContext(); + + if (locale === 'es') { + return ( +
+ + +

Nodos Trabajadores

+

+ Aprende cómo desplegar y gestionar nodos trabajadores de Plexus en cualquier infraestructura para procesar tus tareas de evaluación. +

+ +
+
+

Resumen

+

+ Los nodos trabajadores de Plexus son procesos daemon de larga duración que manejan tareas de evaluación y otras operaciones. + Puedes ejecutar estos trabajadores en cualquier computadora con Python instalado - ya sea en la nube (AWS, Azure, GCP) + o en tus propias instalaciones. +

+

+ Los trabajadores se gestionan usando la herramienta CLI de Plexus, que facilita iniciar, configurar y monitorear procesos + trabajadores en tu infraestructura. +

+
+ +
+

Iniciar un Trabajador

+

+ Usa el comando plexus command worker para iniciar un proceso trabajador. Aquí tienes un ejemplo básico: +

+ +
+              
+ {`plexus command worker \\\\ + --concurrency 4 \\\\ + --queue celery \\\\ + --loglevel INFO`} +
+
+ +
+

--concurrency: Número de procesos trabajadores (predeterminado: 4)

+

--queue: Cola a procesar (predeterminado: celery)

+

--loglevel: Nivel de registro (predeterminado: INFO)

+
+
+ +
+

Especialización de Trabajadores

+

+ Los trabajadores pueden especializarse para manejar tipos específicos de tareas usando patrones objetivo. Esto te permite + dedicar ciertos trabajadores a cargas de trabajo particulares: +

+ +
+              
+ {`# Trabajador que solo procesa tareas relacionadas con conjuntos de datos +plexus command worker \\\\ + --target-patterns "datasets/*" \\\\ + --concurrency 4 + +# Trabajador para tareas intensivas en GPU +plexus command worker \\\\ + --target-patterns "*/gpu-required" \\\\ + --concurrency 2 + +# Trabajador que maneja múltiples tipos de tareas +plexus command worker \\\\ + --target-patterns "datasets/*,training/*" \\\\ + --concurrency 8`} +
+
+ +

+ Los patrones objetivo usan el formato dominio/subdominio y admiten comodines. Algunos ejemplos: +

+
    +
  • datasets/call-criteria - Solo procesar tareas de conjunto de datos de criterios de llamada
  • +
  • training/call-criteria - Solo manejar tareas de entrenamiento de criterios de llamada
  • +
  • */gpu-required - Procesar cualquier tarea que requiera recursos de GPU
  • +
  • datasets/* - Manejar todas las tareas relacionadas con conjuntos de datos
  • +
+
+ +
+

Ejemplos de Despliegue

+

+ Aquí tienes algunos escenarios de despliegue comunes: +

+ +
+
+

AWS EC2

+
+                  
+ {`# Ejecutar en una sesión screen para persistencia +screen -S plexus-worker +plexus command worker \\\\ + --concurrency 8 \\\\ + --loglevel INFO +# Ctrl+A, D para desconectar`} +
+
+
+ +
+

Desarrollo Local

+
+                  
+ {`# Ejecutar con registro aumentado para depuración +plexus command worker \\\\ + --concurrency 2 \\\\ + --loglevel DEBUG`} +
+
+
+ +
+

Trabajador GPU

+
+                  
+ {`# Trabajador GPU dedicado con objetivo específico +plexus command worker \\\\ + --concurrency 1 \\\\ + --target-patterns "*/gpu-required" \\\\ + --loglevel INFO`} +
+
+
+
+
+ +
+

Mejores Prácticas

+
    +
  • Usar un gestor de procesos (como systemd, supervisor, o screen) para mantener los trabajadores funcionando
  • +
  • Establecer concurrencia basada en núcleos de CPU y memoria disponibles
  • +
  • Usar patrones objetivo para optimizar la utilización de recursos
  • +
  • Monitorear registros de trabajadores para errores y problemas de rendimiento
  • +
  • Desplegar trabajadores cerca de tus fuentes de datos cuando sea posible
  • +
  • Considerar usar grupos de auto-escalado en entornos cloud
  • +
+
+ +
+

Recursos Adicionales

+

+ Para más información sobre despliegue y gestión de trabajadores: +

+
    +
  • Consulta la documentación CLI para referencia detallada de comandos
  • +
  • Revisa la ayuda integrada con plexus command worker --help
  • +
  • Ver registros de trabajadores con --loglevel DEBUG para solución de problemas
  • +
+
+
+
+ ); + } + return ( +
+ + +

Worker Nodes

+

+ Learn how to deploy and manage Plexus worker nodes across any infrastructure to process your evaluation tasks. +

+ +
+
+

Overview

+

+ Plexus worker nodes are long-running daemon processes that handle evaluation tasks and other operations. + You can run these workers on any computer with Python installed - whether it's in the cloud (AWS, Azure, GCP) + or on your own premises. +

+

+ Workers are managed using the Plexus CLI tool, which makes it easy to start, configure, and monitor worker + processes across your infrastructure. +

+
+ +
+

Starting a Worker

+

+ Use the plexus command worker command to start a worker process. Here's a basic example: +

+ +
+            
+ {`plexus command worker \\ + --concurrency 4 \\ + --queue celery \\ + --loglevel INFO`} +
+
+ +
+

--concurrency: Number of worker processes (default: 4)

+

--queue: Queue to process (default: celery)

+

--loglevel: Logging level (default: INFO)

+
+
+ +
+

Worker Specialization

+

+ Workers can be specialized to handle specific types of tasks using target patterns. This allows you to + dedicate certain workers to particular workloads: +

+ +
+            
+ {`# Worker that only processes dataset-related tasks +plexus command worker \\ + --target-patterns "datasets/*" \\ + --concurrency 4 + +# Worker for GPU-intensive tasks +plexus command worker \\ + --target-patterns "*/gpu-required" \\ + --concurrency 2 + +# Worker handling multiple task types +plexus command worker \\ + --target-patterns "datasets/*,training/*" \\ + --concurrency 8`} +
+
+ +

+ Target patterns use the format domain/subdomain and support wildcards. Some examples: +

+
    +
  • datasets/call-criteria - Only process call criteria dataset tasks
  • +
  • training/call-criteria - Only handle call criteria training tasks
  • +
  • */gpu-required - Process any tasks requiring GPU resources
  • +
  • datasets/* - Handle all dataset-related tasks
  • +
+
+ +
+

Deployment Examples

+

+ Here are some common deployment scenarios: +

+ +
+
+

AWS EC2

+
+                
+ {`# Run in a screen session for persistence +screen -S plexus-worker +plexus command worker \\ + --concurrency 8 \\ + --loglevel INFO +# Ctrl+A, D to detach`} +
+
+
+ +
+

Local Development

+
+                
+ {`# Run with increased logging for debugging +plexus command worker \\ + --concurrency 2 \\ + --loglevel DEBUG`} +
+
+
+ +
+

GPU Worker

+
+                
+ {`# Dedicated GPU worker with specific targeting +plexus command worker \\ + --concurrency 1 \\ + --target-patterns "*/gpu-required" \\ + --loglevel INFO`} +
+
+
+
+
+ +
+

Best Practices

+
    +
  • Use a process manager (like systemd, supervisor, or screen) to keep workers running
  • +
  • Set concurrency based on available CPU cores and memory
  • +
  • Use target patterns to optimize resource utilization
  • +
  • Monitor worker logs for errors and performance issues
  • +
  • Deploy workers close to your data sources when possible
  • +
  • Consider using auto-scaling groups in cloud environments
  • +
+
+ +
+

Additional Resources

+

+ For more information about worker deployment and management: +

+
    +
  • See the CLI documentation for detailed command reference
  • +
  • Check the built-in help with plexus command worker --help
  • +
  • View worker logs with --loglevel DEBUG for troubleshooting
  • +
+
+
+
+ ) +} \ No newline at end of file diff --git a/dashboard/app/documentation/basics/evaluations/page.tsx b/dashboard/app/[locale]/documentation/basics/evaluations/page.tsx similarity index 100% rename from dashboard/app/documentation/basics/evaluations/page.tsx rename to dashboard/app/[locale]/documentation/basics/evaluations/page.tsx diff --git a/dashboard/app/documentation/components/breadcrumb.tsx b/dashboard/app/[locale]/documentation/components/breadcrumb.tsx similarity index 100% rename from dashboard/app/documentation/components/breadcrumb.tsx rename to dashboard/app/[locale]/documentation/components/breadcrumb.tsx diff --git a/dashboard/app/documentation/components/doc-button.tsx b/dashboard/app/[locale]/documentation/components/doc-button.tsx similarity index 100% rename from dashboard/app/documentation/components/doc-button.tsx rename to dashboard/app/[locale]/documentation/components/doc-button.tsx diff --git a/dashboard/app/documentation/components/documentation-layout.tsx b/dashboard/app/[locale]/documentation/components/documentation-layout.tsx similarity index 58% rename from dashboard/app/documentation/components/documentation-layout.tsx rename to dashboard/app/[locale]/documentation/components/documentation-layout.tsx index 2afe13f0f..1637127d0 100644 --- a/dashboard/app/documentation/components/documentation-layout.tsx +++ b/dashboard/app/[locale]/documentation/components/documentation-layout.tsx @@ -10,7 +10,9 @@ import { useTheme } from "next-themes" import { Button, type ButtonProps } from "@/components/ui/button" import { ScrollArea } from "@/components/ui/scroll-area" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { PublicLanguageSelector } from "@/components/ui/public-language-selector" import SquareLogo, { LogoVariant } from '@/components/logo-square' +import { useTranslationContext } from '@/app/contexts/TranslationContext' const useMediaQuery = (query: string): boolean => { const [matches, setMatches] = useState(false) @@ -76,63 +78,128 @@ interface DocSidebarItem { }>; } -const docSections: DocSidebarItem[] = [ - { - name: "Introduction", - href: "/documentation", - }, - { - name: "Concepts", - href: "/documentation/concepts", - items: [ - { name: "Items", href: "/documentation/concepts/items" }, - { name: "Sources", href: "/documentation/concepts/sources" }, - { name: "Scores", href: "/documentation/concepts/scores" }, - { name: "Scorecards", href: "/documentation/concepts/scorecards" }, - { name: "Score Results", href: "/documentation/concepts/score-results" }, - { name: "Evaluations", href: "/documentation/concepts/evaluations" }, - { name: "Tasks", href: "/documentation/concepts/tasks" }, - { name: "Reports", href: "/documentation/concepts/reports" }, - ], - }, - { - name: "Methods", - href: "/documentation/methods", - items: [ - { name: "Add/Edit a Source", href: "/documentation/methods/add-edit-source" }, - { name: "Profile a Source", href: "/documentation/methods/profile-source" }, - { name: "Add/Edit a Scorecard", href: "/documentation/methods/add-edit-scorecard" }, - { name: "Add/Edit a Score", href: "/documentation/methods/add-edit-score" }, - { name: "Evaluate a Score", href: "/documentation/methods/evaluate-score" }, - { name: "Monitor Tasks", href: "/documentation/methods/monitor-tasks" }, - ], - }, - { - name: "Evaluation Metrics", - href: "/documentation/evaluation-metrics", - items: [ - { name: "Gauges with Context", href: "/documentation/evaluation-metrics/gauges-with-context" }, - { name: "Agreement", href: "/documentation/evaluation-metrics/gauges/agreement" }, - { name: "Accuracy", href: "/documentation/evaluation-metrics/gauges/accuracy" }, - { name: "Precision", href: "/documentation/evaluation-metrics/gauges/precision" }, - { name: "Recall", href: "/documentation/evaluation-metrics/gauges/recall" }, - { name: "Class Number Impact", href: "/documentation/evaluation-metrics/gauges/class-number" }, - { name: "Class Imbalance", href: "/documentation/evaluation-metrics/gauges/class-imbalance" }, - { name: "Examples", href: "/documentation/evaluation-metrics/examples" }, - ], - }, - { - name: "Advanced", - href: "/documentation/advanced", - items: [ - { name: "plexus CLI Tool", href: "/documentation/advanced/cli" }, - { name: "Worker Nodes", href: "/documentation/advanced/worker-nodes" }, - { name: "Python SDK Reference", href: "/documentation/advanced/sdk" }, - { name: "Universal Code Snippets", href: "/documentation/advanced/universal-code" }, - { name: "MCP Server", href: "/documentation/advanced/mcp-server" }, - ], - }, -] +const getDocSections = (locale: string): DocSidebarItem[] => { + const localePrefix = `/${locale}`; + + if (locale === 'es') { + return [ + { + name: "Introducción", + href: `${localePrefix}/documentation`, + }, + { + name: "Conceptos", + href: `${localePrefix}/documentation/concepts`, + items: [ + { name: "Elementos", href: `${localePrefix}/documentation/concepts/items` }, + { name: "Fuentes", href: `${localePrefix}/documentation/concepts/sources` }, + { name: "Puntuaciones", href: `${localePrefix}/documentation/concepts/scores` }, + { name: "Cuadros", href: `${localePrefix}/documentation/concepts/scorecards` }, + { name: "Resultados de Puntuación", href: `${localePrefix}/documentation/concepts/score-results` }, + { name: "Evaluaciones", href: `${localePrefix}/documentation/concepts/evaluations` }, + { name: "Tareas", href: `${localePrefix}/documentation/concepts/tasks` }, + { name: "Reportes", href: `${localePrefix}/documentation/concepts/reports` }, + ], + }, + { + name: "Métodos", + href: `${localePrefix}/documentation/methods`, + items: [ + { name: "Agregar/Editar Fuente", href: `${localePrefix}/documentation/methods/add-edit-source` }, + { name: "Perfilar Fuente", href: `${localePrefix}/documentation/methods/profile-source` }, + { name: "Agregar/Editar Cuadro", href: `${localePrefix}/documentation/methods/add-edit-scorecard` }, + { name: "Agregar/Editar Puntuación", href: `${localePrefix}/documentation/methods/add-edit-score` }, + { name: "Evaluar Puntuación", href: `${localePrefix}/documentation/methods/evaluate-score` }, + { name: "Monitorear Tareas", href: `${localePrefix}/documentation/methods/monitor-tasks` }, + ], + }, + { + name: "Métricas de Evaluación", + href: `${localePrefix}/documentation/evaluation-metrics`, + items: [ + { name: "Indicadores con Contexto", href: `${localePrefix}/documentation/evaluation-metrics/gauges-with-context` }, + { name: "Acuerdo", href: `${localePrefix}/documentation/evaluation-metrics/gauges/agreement` }, + { name: "Precisión", href: `${localePrefix}/documentation/evaluation-metrics/gauges/accuracy` }, + { name: "Exactitud", href: `${localePrefix}/documentation/evaluation-metrics/gauges/precision` }, + { name: "Sensibilidad", href: `${localePrefix}/documentation/evaluation-metrics/gauges/recall` }, + { name: "Impacto del Número de Clases", href: `${localePrefix}/documentation/evaluation-metrics/gauges/class-number` }, + { name: "Desbalance de Clases", href: `${localePrefix}/documentation/evaluation-metrics/gauges/class-imbalance` }, + { name: "Ejemplos", href: `${localePrefix}/documentation/evaluation-metrics/examples` }, + ], + }, + { + name: "Avanzado", + href: `${localePrefix}/documentation/advanced`, + items: [ + { name: "Herramienta CLI plexus", href: `${localePrefix}/documentation/advanced/cli` }, + { name: "Nodos de Trabajo", href: `${localePrefix}/documentation/advanced/worker-nodes` }, + { name: "Referencia SDK Python", href: `${localePrefix}/documentation/advanced/sdk` }, + { name: "Fragmentos de Código Universal", href: `${localePrefix}/documentation/advanced/universal-code` }, + { name: "Servidor MCP", href: `${localePrefix}/documentation/advanced/mcp-server` }, + ], + }, + ]; + } + + // English default + return [ + { + name: "Introduction", + href: `${localePrefix}/documentation`, + }, + { + name: "Concepts", + href: `${localePrefix}/documentation/concepts`, + items: [ + { name: "Items", href: `${localePrefix}/documentation/concepts/items` }, + { name: "Sources", href: `${localePrefix}/documentation/concepts/sources` }, + { name: "Scores", href: `${localePrefix}/documentation/concepts/scores` }, + { name: "Scorecards", href: `${localePrefix}/documentation/concepts/scorecards` }, + { name: "Score Results", href: `${localePrefix}/documentation/concepts/score-results` }, + { name: "Evaluations", href: `${localePrefix}/documentation/concepts/evaluations` }, + { name: "Tasks", href: `${localePrefix}/documentation/concepts/tasks` }, + { name: "Reports", href: `${localePrefix}/documentation/concepts/reports` }, + ], + }, + { + name: "Methods", + href: `${localePrefix}/documentation/methods`, + items: [ + { name: "Add/Edit a Source", href: `${localePrefix}/documentation/methods/add-edit-source` }, + { name: "Profile a Source", href: `${localePrefix}/documentation/methods/profile-source` }, + { name: "Add/Edit a Scorecard", href: `${localePrefix}/documentation/methods/add-edit-scorecard` }, + { name: "Add/Edit a Score", href: `${localePrefix}/documentation/methods/add-edit-score` }, + { name: "Evaluate a Score", href: `${localePrefix}/documentation/methods/evaluate-score` }, + { name: "Monitor Tasks", href: `${localePrefix}/documentation/methods/monitor-tasks` }, + ], + }, + { + name: "Evaluation Metrics", + href: `${localePrefix}/documentation/evaluation-metrics`, + items: [ + { name: "Gauges with Context", href: `${localePrefix}/documentation/evaluation-metrics/gauges-with-context` }, + { name: "Agreement", href: `${localePrefix}/documentation/evaluation-metrics/gauges/agreement` }, + { name: "Accuracy", href: `${localePrefix}/documentation/evaluation-metrics/gauges/accuracy` }, + { name: "Precision", href: `${localePrefix}/documentation/evaluation-metrics/gauges/precision` }, + { name: "Recall", href: `${localePrefix}/documentation/evaluation-metrics/gauges/recall` }, + { name: "Class Number Impact", href: `${localePrefix}/documentation/evaluation-metrics/gauges/class-number` }, + { name: "Class Imbalance", href: `${localePrefix}/documentation/evaluation-metrics/gauges/class-imbalance` }, + { name: "Examples", href: `${localePrefix}/documentation/evaluation-metrics/examples` }, + ], + }, + { + name: "Advanced", + href: `${localePrefix}/documentation/advanced`, + items: [ + { name: "plexus CLI Tool", href: `${localePrefix}/documentation/advanced/cli` }, + { name: "Worker Nodes", href: `${localePrefix}/documentation/advanced/worker-nodes` }, + { name: "Python SDK Reference", href: `${localePrefix}/documentation/advanced/sdk` }, + { name: "Universal Code Snippets", href: `${localePrefix}/documentation/advanced/universal-code` }, + { name: "MCP Server", href: `${localePrefix}/documentation/advanced/mcp-server` }, + ], + }, + ]; +}; interface DocumentationLayoutProps { children: React.ReactNode; @@ -147,9 +214,12 @@ export default function DocumentationLayout({ children, tableOfContents }: Docum const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true) const [isRightSidebarOpen, setIsRightSidebarOpen] = useState(true) const { theme, setTheme } = useTheme() + const { locale } = useTranslationContext() const isDesktop = useMediaQuery("(min-width: 1024px)") const isMobile = useMediaQuery("(max-width: 1023px)") const pathname = usePathname() + + const docSections = getDocSections(locale) useEffect(() => { if (isDesktop) { @@ -232,6 +302,11 @@ export default function DocumentationLayout({ children, tableOfContents }: Docum
+ {isLeftSidebarOpen && ( +
+ +
+ )}
@@ -245,7 +320,10 @@ export default function DocumentationLayout({ children, tableOfContents }: Docum - {isLeftSidebarOpen ? "Toggle sidebar" : "Expand sidebar"} + {isLeftSidebarOpen + ? (locale === 'es' ? 'Alternar barra lateral' : 'Toggle sidebar') + : (locale === 'es' ? 'Expandir barra lateral' : 'Expand sidebar') + } @@ -273,7 +351,10 @@ export default function DocumentationLayout({ children, tableOfContents }: Docum - Toggle {theme === "dark" ? "Light" : theme === "light" ? "System" : "Dark"} Mode + {locale === 'es' + ? `Cambiar a modo ${theme === "dark" ? "Claro" : theme === "light" ? "Sistema" : "Oscuro"}` + : `Toggle ${theme === "dark" ? "Light" : theme === "light" ? "System" : "Dark"} Mode` + } @@ -290,7 +371,9 @@ export default function DocumentationLayout({ children, tableOfContents }: Docum return (
-

On this page

+

+ {locale === 'es' ? 'En esta página' : 'On this page'} +

{ setSearchValue(e.target.value); @@ -3105,7 +3146,7 @@ function ItemsDashboardInner() { className="absolute inset-y-0 right-0 h-9 px-3 rounded-l-none shadow-none" disabled={isSearching} > - {isSearching ? : 'Search'} + {isSearching ? : t('search')} )}
diff --git a/dashboard/components/items/ItemCard.tsx b/dashboard/components/items/ItemCard.tsx index 527c74a07..fb776d54b 100644 --- a/dashboard/components/items/ItemCard.tsx +++ b/dashboard/components/items/ItemCard.tsx @@ -15,6 +15,7 @@ import { IdentifierDisplay } from '@/components/ui/identifier-display' import NumberFlowWrapper from '@/components/ui/number-flow' import ItemScoreResults from '../ItemScoreResults' import { useItemScoreResults } from '@/hooks/useItemScoreResults' +import { useTranslations } from '@/app/contexts/TranslationContext' import { MetadataEditor } from '@/components/ui/metadata-editor' import { FileAttachments } from './FileAttachments' import { @@ -120,6 +121,8 @@ const ItemCard = React.forwardRef(({ className, ...props }, ref) => { + const t = useTranslations('scorecards') + const tItems = useTranslations('items') const [isNarrowViewport, setIsNarrowViewport] = React.useState(false) // Use the score results hook for detail view @@ -197,8 +200,8 @@ const ItemCard = React.forwardRef(({ {hasMultipleScorecards ? - `${item.scorecards.length} scorecards` : - item.scorecards[0]?.scorecardName || 'Scorecard' + `${item.scorecards.length} ${t('scorecards')}` : + item.scorecards[0]?.scorecardName || t('scorecard') }
@@ -209,7 +212,7 @@ const ItemCard = React.forwardRef(({
- score result{totalResults !== 1 ? 's' : ''} + {t(totalResults === 1 ? 'scoreResult' : 'scoreResults')}
)} @@ -249,8 +252,8 @@ const ItemCard = React.forwardRef(({
{item.icon || } -
- Item +
+ {tItems('item')}
@@ -276,7 +279,7 @@ const ItemCard = React.forwardRef(({
-

Item Details

+

{tItems('itemDetails')}

(({ {/* Total results summary */} {totalResults > 0 && (
- score result{totalResults !== 1 ? 's' : ''} across scorecard{item.scorecards.length !== 1 ? 's' : ''} + {t(totalResults === 1 ? 'scoreResultAcross' : 'scoreResultsAcross')} {t(item.scorecards.length === 1 ? 'scorecard' : 'scorecards')}
)}
@@ -319,7 +322,7 @@ const ItemCard = React.forwardRef(({ onSelect={onViewData} > - View Details + {tItems('viewDetails')} )} @@ -351,7 +354,7 @@ const ItemCard = React.forwardRef(({ !item.description.match(/^API Call - Report \d+$/) && !item.description.match(/^(Call|Report|Session|Item) - .+$/) && (
-

Description

+

{tItems('description')}

{item.description}

@@ -366,7 +369,7 @@ const ItemCard = React.forwardRef(({
- Text + {tItems('text')}
@@ -404,8 +407,8 @@ const ItemCard = React.forwardRef(({
- Metadata - {!readOnly && optional} + {tItems('metadata')} + {!readOnly && {tItems('optional')}}
@@ -430,7 +433,7 @@ const ItemCard = React.forwardRef(({
- Attached Files + {tItems('attachedFiles')}
diff --git a/dashboard/components/items/ScoreResultCard.tsx b/dashboard/components/items/ScoreResultCard.tsx index ac32ab220..58730f777 100644 --- a/dashboard/components/items/ScoreResultCard.tsx +++ b/dashboard/components/items/ScoreResultCard.tsx @@ -14,6 +14,7 @@ import { IdentifierDisplay } from '@/components/ui/identifier-display' import { ScoreResultTrace } from '@/components/ui/score-result-trace' import FileContentViewer from '@/components/ui/FileContentViewer' import { getDashboardUrl } from '@/utils/plexus-links'; +import { useTranslations } from '@/app/contexts/TranslationContext'; import { Accordion, AccordionContent, @@ -66,6 +67,8 @@ const ScoreResultCard = React.forwardRef(( className, ...props }, ref) => { + const t = useTranslations('items'); + React.useEffect(() => { console.log('[ScoreResultCard] Received scoreResult:', scoreResult); if (scoreResult) { @@ -95,7 +98,7 @@ const ScoreResultCard = React.forwardRef((
-

Score Result Details

+

{t('scoreResultDetails')}

(( className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50" onSelect={() => {}} > - View Raw Data + {t('viewRawData')} @@ -173,7 +176,7 @@ const ScoreResultCard = React.forwardRef(( )} @@ -181,7 +184,7 @@ const ScoreResultCard = React.forwardRef(( )} @@ -192,12 +195,12 @@ const ScoreResultCard = React.forwardRef(( {/* Value */}
-

Value

+

{t('value')}

{scoreResult.value} {scoreResult.confidence !== null && scoreResult.confidence !== undefined && ( - {Math.round((scoreResult.confidence || 0) * 100)}% confidence + {Math.round((scoreResult.confidence || 0) * 100)}% {t('confidence')} )}
@@ -206,7 +209,7 @@ const ScoreResultCard = React.forwardRef(( {/* Explanation */} {scoreResult.explanation && (
-

Explanation

+

{t('explanation')}

(( {/* Attachments */} {scoreResult.attachments && scoreResult.attachments.length > 0 && (
-

Attachments

+

{t('attachments')}

{scoreResult.attachments.map((attachmentPath, index) => ( { { ) : ( { console.log("ReportsDashboard mounted. Block registry setup should have run."); - }, []); + }, []); + const tReports = useTranslations('reports'); const { user } = useAuthenticator() const router = useRouter() @@ -1327,7 +1329,7 @@ export default function ReportsDashboard({ variant="ghost" className="bg-card hover:bg-accent text-muted-foreground" > - Edit Configurations + {tReports('editConfigurations')}
diff --git a/dashboard/components/scorecards-dashboard.tsx b/dashboard/components/scorecards-dashboard.tsx index 354dc6f97..8e387dc8c 100644 --- a/dashboard/components/scorecards-dashboard.tsx +++ b/dashboard/components/scorecards-dashboard.tsx @@ -1,5 +1,6 @@ "use client" import React, { useState, useEffect } from "react" +import { useTranslations } from '@/app/contexts/TranslationContext' import { Button } from "./ui/button" import { amplifyClient, getClient } from "@/utils/amplify-client" import type { Schema } from "@/amplify/data/resource" @@ -54,6 +55,7 @@ export default function ScorecardsComponent({ } = {}) { // Get the Amplify client for Tasks model const client = getClient(); + const tScorecards = useTranslations('scorecards'); const [scorecards, setScorecards] = useState([]) const [isLoading, setIsLoading] = useState(true) @@ -1063,7 +1065,7 @@ export default function ScorecardsComponent({ variant="ghost" className="bg-card hover:bg-accent text-muted-foreground" > - New Scorecard + {tScorecards('newScorecard')}
diff --git a/dashboard/components/task-dispatch/RunReportButton.tsx b/dashboard/components/task-dispatch/RunReportButton.tsx index 922f84b2d..514f3fce1 100644 --- a/dashboard/components/task-dispatch/RunReportButton.tsx +++ b/dashboard/components/task-dispatch/RunReportButton.tsx @@ -7,11 +7,13 @@ import { createTask } from "@/utils/data-operations" import { toast } from "sonner" import { ReportConfigurationDialog } from "./dialogs/ReportConfigurationDialog" import { useAccount } from "@/app/contexts/AccountContext" +import { useTranslations } from '@/app/contexts/TranslationContext' export function RunReportButton() { const [isModalOpen, setIsModalOpen] = useState(false) const [isLoading, setIsLoading] = useState(false) const { selectedAccount } = useAccount() + const tReports = useTranslations('reports') const handleOpenDialog = () => { setIsModalOpen(true) @@ -53,7 +55,7 @@ export function RunReportButton() { // Mock action object for the dialog const mockAction = { - name: "Run Report", + name: tReports('runReport'), icon: , command: "report run", target: "report", @@ -68,7 +70,7 @@ export function RunReportButton() { variant="ghost" className="bg-card hover:bg-accent text-muted-foreground" > - Run Report + {tReports('runReport')} string): TaskDispatchConfig => ({ + buttonLabel: t('run'), actions: [ { - name: "Evaluate Accuracy", + name: t('evaluateAccuracy'), icon: , command: "evaluate accuracy", target: "evaluation", @@ -14,7 +14,7 @@ export const evaluationsConfig: TaskDispatchConfig = { description: "Evaluate model accuracy against ground truth" }, { - name: "Evaluate Consistency", + name: t('evaluateConsistency'), icon: , command: "evaluate consistency", target: "evaluation", @@ -22,7 +22,7 @@ export const evaluationsConfig: TaskDispatchConfig = { description: "Evaluate model consistency across similar inputs" }, { - name: "Evaluate Alignment", + name: t('evaluateAlignment'), icon: , command: "evaluate alignment", target: "evaluation", @@ -33,4 +33,4 @@ export const evaluationsConfig: TaskDispatchConfig = { dialogs: { evaluation: EvaluationDialog } -} \ No newline at end of file +}) \ No newline at end of file diff --git a/dashboard/components/task-dispatch/configs/reports.tsx b/dashboard/components/task-dispatch/configs/reports.tsx index d342b8963..246b54aeb 100644 --- a/dashboard/components/task-dispatch/configs/reports.tsx +++ b/dashboard/components/task-dispatch/configs/reports.tsx @@ -2,11 +2,11 @@ import { Play } from "lucide-react" import { TaskDispatchConfig } from "../types" import { ReportConfigurationDialog } from "../dialogs/ReportConfigurationDialog" -export const reportsConfig: TaskDispatchConfig = { - buttonLabel: "Run Report", +export const createReportsConfig = (t: (key: string) => string): TaskDispatchConfig => ({ + buttonLabel: t('runReport'), actions: [ { - name: "Run Report", + name: t('runReport'), icon: , command: "report run", // Base command, will be extended with config ID target: "report", @@ -17,4 +17,4 @@ export const reportsConfig: TaskDispatchConfig = { dialogs: { reportConfiguration: ReportConfigurationDialog } -} \ No newline at end of file +}) \ No newline at end of file diff --git a/dashboard/components/ui/language-selector.tsx b/dashboard/components/ui/language-selector.tsx new file mode 100644 index 000000000..16ca22752 --- /dev/null +++ b/dashboard/components/ui/language-selector.tsx @@ -0,0 +1,73 @@ +"use client"; + +import * as React from "react"; +import { useRouter, usePathname } from "next/navigation"; +import { useLocale, useTranslations } from "@/app/contexts/TranslationContext"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { locales } from "@/i18n"; + +interface LanguageSelectorProps { + variant?: "compact" | "full"; +} + +const languageNames = { + en: "English", + es: "Español", +} as const; + +export function LanguageSelector({ variant = "full" }: LanguageSelectorProps) { + const t = useTranslations("languages"); + const locale = useLocale(); + const router = useRouter(); + const pathname = usePathname(); + + const handleLanguageChange = (newLocale: string) => { + // Remove the current locale from the pathname + const pathWithoutLocale = pathname.replace(`/${locale}`, ""); + // Navigate to the new locale + router.push(`/${newLocale}${pathWithoutLocale}`); + }; + + if (variant === "compact") { + return ( + + ); + } + + return ( +
+ + +
+ ); +} \ No newline at end of file diff --git a/dashboard/components/ui/public-language-selector.tsx b/dashboard/components/ui/public-language-selector.tsx new file mode 100644 index 000000000..434a8d297 --- /dev/null +++ b/dashboard/components/ui/public-language-selector.tsx @@ -0,0 +1,47 @@ +"use client"; + +import * as React from "react"; +import { useRouter, usePathname, useParams } from "next/navigation"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +const locales = ['en', 'es'] as const; + +const languageNames = { + en: "English", + es: "Español", +} as const; + +export function PublicLanguageSelector() { + const params = useParams(); + const locale = (params.locale as string) || 'en'; + const router = useRouter(); + const pathname = usePathname(); + + const handleLanguageChange = (newLocale: string) => { + // Remove the current locale from the pathname + const pathWithoutLocale = pathname.replace(`/${locale}`, ""); + // Navigate to the new locale + router.push(`/${newLocale}${pathWithoutLocale}`); + }; + + return ( + + ); +} \ No newline at end of file diff --git a/dashboard/components/ui/timestamp.tsx b/dashboard/components/ui/timestamp.tsx index 69bd8b832..5054c6191 100644 --- a/dashboard/components/ui/timestamp.tsx +++ b/dashboard/components/ui/timestamp.tsx @@ -3,6 +3,7 @@ import { Clock, Timer } from 'lucide-react' import { formatDistanceToNow, formatDuration, intervalToDuration, format, Duration } from 'date-fns' import { cn } from '@/lib/utils' import NumberFlowWrapper from './number-flow' +import { useTranslations } from '@/app/contexts/TranslationContext' export interface TimestampProps { /** @@ -31,7 +32,7 @@ export interface TimestampProps { skeletonMode?: boolean } -const formatElapsedTime = (start: Date, end: Date): { type: 'string', value: string } | { type: 'component', value: React.ReactNode } => { +const formatElapsedTime = (start: Date, end: Date, t: (key: string, variables?: Record) => string): { type: 'string', value: string } | { type: 'component', value: React.ReactNode } => { const duration = intervalToDuration({ start, end }) // Calculate total minutes to determine if we should show seconds @@ -43,32 +44,36 @@ const formatElapsedTime = (start: Date, end: Date): { type: 'string', value: str if (totalMinutes >= 1) { // Show hours and minutes if (duration.hours && duration.hours > 0) { + const hourText = duration.hours === 1 ? 'hour' : 'hours'; parts.push( - hour{duration.hours === 1 ? '' : 's'} + {t(hourText)} ) } if (duration.minutes && duration.minutes > 0) { + const minuteText = duration.minutes === 1 ? 'minute' : 'minutes'; parts.push( - minute{duration.minutes === 1 ? '' : 's'} + {t(minuteText)} ) } } else { // Show minutes and seconds if (duration.minutes && duration.minutes > 0) { + const minuteText = duration.minutes === 1 ? 'minute' : 'minutes'; parts.push( - minute{duration.minutes === 1 ? '' : 's'} + {t(minuteText)} ) } if (duration.seconds && duration.seconds > 0) { + const secondText = duration.seconds === 1 ? 'second' : 'seconds'; parts.push( - second{duration.seconds === 1 ? '' : 's'} + {t(secondText)} ) } @@ -91,7 +96,7 @@ const areDatesMeaningfullyDifferent = (start: Date, end: Date): boolean => { return diffInMs > 1000 // More than 1 second difference } -const formatRelativeTime = (date: Date): { type: 'string', value: string } | { type: 'component', value: React.ReactNode } => { +const formatRelativeTime = (date: Date, t: (key: string, variables?: Record) => string): { type: 'string', value: string } | { type: 'component', value: React.ReactNode } => { try { // Check if date is valid if (isNaN(date.getTime())) { @@ -103,88 +108,70 @@ const formatRelativeTime = (date: Date): { type: 'string', value: string } | { t // Handle future dates if (diffInSeconds < 0) { - return { type: 'string', value: 'in the future' }; + return { type: 'string', value: t('inTheFuture') }; } // Less than 1 minute if (diffInSeconds < 60) { - return { type: 'string', value: '<1 minute ago' }; + return { type: 'string', value: t('lessThanMinute') }; } // Minutes const diffInMinutes = Math.floor(diffInSeconds / 60); if (diffInMinutes < 60) { + const timeKey = diffInMinutes === 1 ? 'minuteAgo' : 'minutesAgo'; return { - type: 'component', - value: ( - - minute{diffInMinutes === 1 ? '' : 's'} ago - - ) + type: 'string', + value: t(timeKey, { count: diffInMinutes }) }; } // Hours const diffInHours = Math.floor(diffInMinutes / 60); if (diffInHours < 24) { + const timeKey = diffInHours === 1 ? 'hourAgo' : 'hoursAgo'; return { - type: 'component', - value: ( - - hour{diffInHours === 1 ? '' : 's'} ago - - ) + type: 'string', + value: t(timeKey, { count: diffInHours }) }; } // Days const diffInDays = Math.floor(diffInHours / 24); if (diffInDays < 7) { + const timeKey = diffInDays === 1 ? 'dayAgo' : 'daysAgo'; return { - type: 'component', - value: ( - - day{diffInDays === 1 ? '' : 's'} ago - - ) + type: 'string', + value: t(timeKey, { count: diffInDays }) }; } // Weeks const diffInWeeks = Math.floor(diffInDays / 7); if (diffInWeeks < 4) { + const timeKey = diffInWeeks === 1 ? 'weekAgo' : 'weeksAgo'; return { - type: 'component', - value: ( - - week{diffInWeeks === 1 ? '' : 's'} ago - - ) + type: 'string', + value: t(timeKey, { count: diffInWeeks }) }; } // Months const diffInMonths = Math.floor(diffInDays / 30); if (diffInMonths < 12) { + const timeKey = diffInMonths === 1 ? 'monthAgo' : 'monthsAgo'; return { - type: 'component', - value: ( - - month{diffInMonths === 1 ? '' : 's'} ago - - ) + type: 'string', + value: t(timeKey, { count: diffInMonths }) }; } // Years const diffInYears = Math.floor(diffInDays / 365); + const timeKey = diffInYears === 1 ? 'yearAgo' : 'yearsAgo'; return { - type: 'component', - value: ( - - year{diffInYears === 1 ? '' : 's'} ago - - ) + type: 'string', + value: t(timeKey, { count: diffInYears }) }; } catch (e) { @@ -213,6 +200,7 @@ export function Timestamp({ className, skeletonMode = false }: TimestampProps) { + const t = useTranslations('time') const [displayContent, setDisplayContent] = useState('') const [showAbsolute, setShowAbsolute] = useState(false) const timeDate = typeof time === 'string' ? new Date(time) : time @@ -239,7 +227,7 @@ export function Timestamp({ // Only show elapsed time if the dates are meaningfully different if (areDatesMeaningfullyDifferent(timeDate, endTime)) { - const result = formatElapsedTime(timeDate, endTime) + const result = formatElapsedTime(timeDate, endTime, t) setDisplayContent(result.type === 'component' ? result.value : result.value) } else { setDisplayContent('') // Return empty string if times are effectively the same @@ -248,7 +236,7 @@ export function Timestamp({ if (showAbsolute) { setDisplayContent(formatAbsoluteTime(timeDate)) } else { - const result = formatRelativeTime(timeDate) + const result = formatRelativeTime(timeDate, t) setDisplayContent(result.type === 'component' ? result.value : result.value) } } diff --git a/dashboard/i18n.ts b/dashboard/i18n.ts new file mode 100644 index 000000000..9025c9f37 --- /dev/null +++ b/dashboard/i18n.ts @@ -0,0 +1,16 @@ +import {notFound} from 'next/navigation'; +import {getRequestConfig} from 'next-intl/server'; + +// Can be imported from a shared config +export const locales = ['en', 'es'] as const; +export type Locale = (typeof locales)[number]; + +export default getRequestConfig(async ({locale}) => { + // Validate that the incoming `locale` parameter is valid + if (!locales.includes(locale as any)) notFound(); + + return { + messages: (await import(`./messages/${locale}.json`)).default, + timeZone: 'America/New_York' + }; +}); \ No newline at end of file diff --git a/dashboard/messages/en.json b/dashboard/messages/en.json new file mode 100644 index 000000000..df3bb5b58 --- /dev/null +++ b/dashboard/messages/en.json @@ -0,0 +1,339 @@ +{ + "navigation": { + "items": "Items", + "evaluations": "Evaluations", + "reports": "Reports", + "scorecards": "Scorecards", + "sources": "Sources", + "source": "Source", + "batches": "Batches", + "activity": "Activity", + "feedback": "Feedback", + "alerts": "Alerts", + "help": "Help" + }, + "time": { + "lessThanMinute": "<1 minute ago", + "minuteAgo": "{count} minute ago", + "minutesAgo": "{count} minutes ago", + "hourAgo": "{count} hour ago", + "hoursAgo": "{count} hours ago", + "dayAgo": "{count} day ago", + "daysAgo": "{count} days ago", + "weekAgo": "{count} week ago", + "weeksAgo": "{count} weeks ago", + "monthAgo": "{count} month ago", + "monthsAgo": "{count} months ago", + "yearAgo": "{count} year ago", + "yearsAgo": "{count} years ago", + "inTheFuture": "in the future", + "invalidDate": "Invalid date", + "now": "Now", + "today": "Today", + "yesterday": "Yesterday", + "thisWeek": "This Week", + "lastWeek": "Last Week", + "thisMonth": "This Month", + "lastMonth": "Last Month", + "ago": "ago", + "second": "second", + "seconds": "seconds", + "minute": "minute", + "minutes": "minutes", + "hour": "hour", + "hours": "hours", + "day": "day", + "days": "days", + "week": "week", + "weeks": "weeks", + "month": "month", + "months": "months", + "year": "year", + "years": "years" + }, + "common": { + "loading": "Loading...", + "error": "Error", + "success": "Success", + "save": "Save", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit", + "create": "Create", + "update": "Update", + "close": "Close", + "confirm": "Confirm", + "search": "Search", + "filter": "Filter", + "export": "Export", + "import": "Import", + "refresh": "Refresh", + "settings": "Settings", + "help": "Help", + "language": "Language", + "theme": "Theme", + "dashboard": "Dashboard", + "name": "Name", + "description": "Description", + "status": "Status", + "date": "Date", + "time": "Time", + "type": "Type", + "value": "Value", + "actions": "Actions", + "details": "Details", + "overview": "Overview", + "back": "Back", + "next": "Next", + "previous": "Previous", + "yes": "Yes", + "no": "No" + }, + "dashboard": { + "title": "Dashboard", + "welcome": "Welcome to Plexus", + "overview": "Overview", + "recentActivity": "Recent Activity", + "quickActions": "Quick Actions", + "statistics": "Statistics", + "performance": "Performance" + }, + "evaluations": { + "title": "Evaluations", + "create": "Create Evaluation", + "list": "Evaluation List", + "details": "Evaluation Details", + "results": "Results", + "metrics": "Metrics", + "accuracy": "Accuracy", + "precision": "Precision", + "recall": "Recall", + "f1Score": "F1 Score", + "scoreResults": "Score Results", + "noEvaluations": "No evaluations found", + "runEvaluation": "Run Evaluation", + "evaluationComplete": "Evaluation Complete", + "evaluationFailed": "Evaluation Failed", + "evaluationInProgress": "Evaluation In Progress", + "run": "Run", + "share": "Share", + "delete": "Delete", + "evaluateAccuracy": "Evaluate Accuracy", + "evaluateConsistency": "Evaluate Consistency", + "evaluateAlignment": "Evaluate Alignment" + }, + "scorecards": { + "title": "Scorecards", + "newScorecard": "New Scorecard", + "create": "Create Scorecard", + "edit": "Edit Scorecard", + "list": "Scorecard List", + "details": "Scorecard Details", + "scores": "Scores", + "scorecard": "Scorecard", + "scorecards": "Scorecards", + "allScorecards": "All Scorecards", + "score": "Score", + "allScores": "All Scores", + "scoreResult": "score result", + "scoreResults": "score results", + "scoreResultAcross": "score result across", + "scoreResultsAcross": "score results across", + "sections": "Sections", + "noScorecards": "No scorecards found", + "addScore": "Add Score", + "addSection": "Add Section", + "testScore": "Test Score", + "testScorecard": "Test Scorecard", + "confidence": "confidence" + }, + "items": { + "title": "Items", + "item": "Item", + "itemDetails": "Item Details", + "list": "Item List", + "details": "Item Details", + "metadata": "Metadata", + "content": "Content", + "noItems": "No items found", + "fileAttachments": "File Attachments", + "scoreResults": "Score Results", + "searchByIdentifier": "Search by identifier", + "search": "Search", + "loadingMoreItems": "Loading more items...", + "scoreResultDetails": "Score Result Details", + "description": "Description", + "text": "Text", + "metadata": "Metadata", + "optional": "optional", + "attachedFiles": "Attached Files", + "viewDetails": "View Details", + "value": "Value", + "explanation": "Explanation", + "attachments": "Attachments", + "viewRawData": "View Raw Data", + "exitFullWidth": "Exit full width", + "fullWidth": "Full width", + "close": "Close", + "confidence": "confidence" + }, + "reports": { + "title": "Reports", + "create": "Create Report", + "list": "Report List", + "configuration": "Configuration", + "generate": "Generate Report", + "download": "Download Report", + "noReports": "No reports found", + "reportGenerated": "Report Generated", + "reportFailed": "Report Generation Failed", + "runReport": "Run Report", + "editConfigurations": "Edit Configurations" + }, + "datasets": { + "title": "Datasets", + "create": "Create Dataset", + "list": "Dataset List", + "configuration": "Dataset Configuration", + "noDatasets": "No datasets found", + "upload": "Upload Dataset", + "process": "Process Dataset" + }, + "batches": { + "title": "Batches", + "list": "Batch List", + "details": "Batch Details", + "progress": "Progress", + "noBatches": "No batches found", + "status": { + "pending": "Pending", + "processing": "Processing", + "completed": "Completed", + "failed": "Failed" + } + }, + "tasks": { + "title": "Tasks", + "list": "Task List", + "details": "Task Details", + "dispatch": "Dispatch Task", + "monitor": "Monitor Tasks", + "noTasks": "No tasks found", + "taskComplete": "Task Complete", + "taskFailed": "Task Failed", + "taskInProgress": "Task In Progress" + }, + "activity": { + "title": "Activity", + "recent": "Recent Activity", + "filter": "Filter Activity", + "noActivity": "No activity found" + }, + "alerts": { + "title": "Alerts", + "list": "Alert List", + "create": "Create Alert", + "noAlerts": "No alerts found", + "severity": { + "low": "Low", + "medium": "Medium", + "high": "High", + "critical": "Critical" + } + }, + "feedback": { + "title": "Feedback", + "list": "Feedback List", + "provide": "Provide Feedback", + "noFeedback": "No feedback found", + "submit": "Submit Feedback" + }, + "settings": { + "title": "Settings", + "description": "Manage your account and application settings.", + "account": { + "title": "Account Settings", + "description": "Customize your account menu visibility settings.", + "menuVisibilityTitle": "Menu Visibility for {accountName}", + "menuVisibilityDescription": "Choose which menu items to show or hide in the sidebar.", + "noAccountSelected": "No account selected", + "settingsSaved": "Account settings saved successfully", + "settingsSaveError": "Failed to save account settings", + "saving": "Saving...", + "saveChanges": "Save Changes" + }, + "user": "User Settings", + "language": "Language Settings", + "theme": "Theme Settings", + "notifications": "Notifications", + "privacy": "Privacy", + "security": "Security", + "defaultLanguage": "Default Language", + "userLanguage": "User Language", + "browserLanguage": "Use Browser Language" + }, + "documentation": { + "title": "Documentation", + "concepts": "Concepts", + "methods": "Methods", + "basics": "Basics", + "advanced": "Advanced", + "evaluationMetrics": "Evaluation Metrics", + "examples": "Examples", + "getStarted": "Get Started", + "quickStart": "Quick Start", + "api": "API Reference", + "sdk": "SDK", + "cli": "CLI", + "mcpServer": "MCP Server", + "workerNodes": "Worker Nodes", + "universalCode": "Universal Code" + }, + "solutions": { + "title": "Solutions", + "callCenterQA": "Call Center QA", + "enterprise": "Enterprise", + "optimizerAgents": "Optimizer Agents", + "platform": "Platform", + "resources": "Resources" + }, + "errors": { + "general": "An error occurred", + "notFound": "Not found", + "unauthorized": "Unauthorized", + "forbidden": "Forbidden", + "serverError": "Server error", + "networkError": "Network error", + "validationError": "Validation error", + "required": "This field is required", + "invalidFormat": "Invalid format", + "tooShort": "Too short", + "tooLong": "Too long" + }, + "success": { + "saved": "Saved successfully", + "created": "Created successfully", + "updated": "Updated successfully", + "deleted": "Deleted successfully", + "uploaded": "Uploaded successfully", + "exported": "Exported successfully", + "imported": "Imported successfully" + }, + "metrics": { + "accuracy": "Accuracy", + "precision": "Precision", + "recall": "Recall", + "f1Score": "F1 Score", + "agreement": "Agreement", + "classImbalance": "Class Imbalance", + "classNumber": "Class Number", + "gauges": "Gauges", + "beforeAfter": "Before/After", + "distribution": "Distribution", + "confusion": "Confusion Matrix" + }, + "languages": { + "en": "English", + "es": "Español" + } +} \ No newline at end of file diff --git a/dashboard/messages/es.json b/dashboard/messages/es.json new file mode 100644 index 000000000..9eee097f9 --- /dev/null +++ b/dashboard/messages/es.json @@ -0,0 +1,360 @@ +{ + "navigation": { + "items": "Elementos", + "evaluations": "Evaluaciones", + "reports": "Reportes", + "scorecards": "Cuadros", + "sources": "Fuentes", + "source": "Fuente", + "batches": "Lotes", + "activity": "Actividad", + "feedback": "Comentarios", + "alerts": "Alertas", + "help": "Ayuda" + }, + "time": { + "lessThanMinute": "hace <1 minuto", + "minuteAgo": "hace {count} minuto", + "minutesAgo": "hace {count} minutos", + "hourAgo": "hace {count} hora", + "hoursAgo": "hace {count} horas", + "dayAgo": "hace {count} día", + "daysAgo": "hace {count} días", + "weekAgo": "hace {count} semana", + "weeksAgo": "hace {count} semanas", + "monthAgo": "hace {count} mes", + "monthsAgo": "hace {count} meses", + "yearAgo": "hace {count} año", + "yearsAgo": "hace {count} años", + "inTheFuture": "en el futuro", + "invalidDate": "Fecha inválida", + "now": "Ahora", + "today": "Hoy", + "yesterday": "Ayer", + "thisWeek": "Esta Semana", + "lastWeek": "Semana Pasada", + "thisMonth": "Este Mes", + "lastMonth": "Mes Pasado", + "ago": "hace", + "second": "segundo", + "seconds": "segundos", + "minute": "minuto", + "minutes": "minutos", + "hour": "hora", + "hours": "horas", + "day": "día", + "days": "días", + "week": "semana", + "weeks": "semanas", + "month": "mes", + "months": "meses", + "year": "año", + "years": "años" + }, + "common": { + "loading": "Cargando...", + "error": "Error", + "success": "Éxito", + "save": "Guardar", + "cancel": "Cancelar", + "delete": "Eliminar", + "edit": "Editar", + "create": "Crear", + "update": "Actualizar", + "close": "Cerrar", + "confirm": "Confirmar", + "search": "Buscar", + "filter": "Filtrar", + "export": "Exportar", + "import": "Importar", + "refresh": "Actualizar", + "settings": "Configuración", + "help": "Ayuda", + "language": "Idioma", + "theme": "Tema", + "dashboard": "Panel de Control", + "name": "Nombre", + "description": "Descripción", + "status": "Estado", + "date": "Fecha", + "time": "Hora", + "type": "Tipo", + "value": "Valor", + "actions": "Acciones", + "details": "Detalles", + "overview": "Resumen", + "back": "Atrás", + "next": "Siguiente", + "previous": "Anterior", + "yes": "Sí", + "no": "No" + }, + "navigation": { + "dashboard": "Panel de Control", + "evaluations": "Evaluaciones", + "scorecards": "Cuadros", + "items": "Elementos", + "reports": "Informes", + "datasets": "Conjuntos de Datos", + "batches": "Lotes", + "tasks": "Tareas", + "activity": "Actividad", + "alerts": "Alertas", + "feedback": "Comentarios", + "feedbackQueues": "Colas de Comentarios", + "settings": "Configuración", + "documentation": "Documentación", + "sources": "Fuentes", + "help": "Ayuda", + "lab": "Laboratorio", + "platform": "Plataforma", + "solutions": "Soluciones" + }, + "dashboard": { + "title": "Panel de Control", + "welcome": "Bienvenido a Plexus", + "overview": "Resumen", + "recentActivity": "Actividad Reciente", + "quickActions": "Acciones Rápidas", + "statistics": "Estadísticas", + "performance": "Rendimiento" + }, + "evaluations": { + "title": "Evaluaciones", + "create": "Crear Evaluación", + "list": "Lista de Evaluaciones", + "details": "Detalles de Evaluación", + "results": "Resultados", + "metrics": "Métricas", + "accuracy": "Precisión", + "precision": "Exactitud", + "recall": "Recuperación", + "f1Score": "Puntuación F1", + "scoreResults": "Resultados de Puntuación", + "noEvaluations": "No se encontraron evaluaciones", + "runEvaluation": "Ejecutar Evaluación", + "evaluationComplete": "Evaluación Completada", + "evaluationFailed": "Evaluación Fallida", + "evaluationInProgress": "Evaluación en Progreso", + "run": "Ejecutar", + "share": "Compartir", + "delete": "Eliminar", + "evaluateAccuracy": "Evaluar Precisión", + "evaluateConsistency": "Evaluar Consistencia", + "evaluateAlignment": "Evaluar Alineación" + }, + "scorecards": { + "title": "Cuadros", + "newScorecard": "Nuevo Cuadro", + "create": "Crear Cuadro", + "edit": "Editar Cuadro", + "list": "Lista de Cuadros", + "details": "Detalles de Cuadro", + "scores": "Puntajes", + "scorecard": "Cuadro", + "scorecards": "Cuadros", + "allScorecards": "Todos los Cuadros", + "score": "Puntaje", + "allScores": "Todos los Puntajes", + "scoreResult": "resultado de puntaje", + "scoreResults": "Resultados de Puntuación", + "scoreResultAcross": "resultado de puntaje en", + "scoreResultsAcross": "resultados de puntaje en", + "sections": "Secciones", + "noScorecards": "No se encontraron cuadros", + "addScore": "Agregar Puntuación", + "addSection": "Agregar Sección", + "testScore": "Probar Puntuación", + "testScorecard": "Probar Tarjeta de Puntuación", + "confidence": "confianza" + }, + "items": { + "title": "Elementos", + "item": "Elemento", + "itemDetails": "Detalles del Elemento", + "list": "Lista de Elementos", + "details": "Detalles del Elemento", + "metadata": "Metadatos", + "content": "Contenido", + "noItems": "No se encontraron elementos", + "fileAttachments": "Archivos Adjuntos", + "scoreResults": "Resultados de Puntuación", + "searchByIdentifier": "Buscar por identificador", + "search": "Buscar", + "loadingMoreItems": "Cargando más elementos...", + "scoreResultDetails": "Detalles del Resultado de Puntuación", + "description": "Descripción", + "text": "Texto", + "metadata": "Metadatos", + "optional": "opcional", + "attachedFiles": "Archivos Adjuntos", + "viewDetails": "Ver Detalles", + "value": "Valor", + "explanation": "Explicación", + "attachments": "Adjuntos", + "viewRawData": "Ver Datos en Bruto", + "exitFullWidth": "Salir de pantalla completa", + "fullWidth": "Pantalla completa", + "close": "Cerrar", + "confidence": "confianza" + }, + "reports": { + "title": "Informes", + "create": "Crear Informe", + "list": "Lista de Informes", + "configuration": "Configuración", + "generate": "Generar Informe", + "download": "Descargar Informe", + "noReports": "No se encontraron informes", + "reportGenerated": "Informe Generado", + "reportFailed": "Generación de Informe Fallida", + "runReport": "Ejecutar Informe", + "editConfigurations": "Editar Configuraciones" + }, + "datasets": { + "title": "Conjuntos de Datos", + "create": "Crear Conjunto de Datos", + "list": "Lista de Conjuntos de Datos", + "configuration": "Configuración del Conjunto de Datos", + "noDatasets": "No se encontraron conjuntos de datos", + "upload": "Subir Conjunto de Datos", + "process": "Procesar Conjunto de Datos" + }, + "batches": { + "title": "Lotes", + "list": "Lista de Lotes", + "details": "Detalles del Lote", + "progress": "Progreso", + "noBatches": "No se encontraron lotes", + "status": { + "pending": "Pendiente", + "processing": "Procesando", + "completed": "Completado", + "failed": "Fallido" + } + }, + "tasks": { + "title": "Tareas", + "list": "Lista de Tareas", + "details": "Detalles de la Tarea", + "dispatch": "Enviar Tarea", + "monitor": "Monitorear Tareas", + "noTasks": "No se encontraron tareas", + "taskComplete": "Tarea Completada", + "taskFailed": "Tarea Fallida", + "taskInProgress": "Tarea en Progreso" + }, + "activity": { + "title": "Actividad", + "recent": "Actividad Reciente", + "filter": "Filtrar Actividad", + "noActivity": "No se encontró actividad" + }, + "alerts": { + "title": "Alertas", + "list": "Lista de Alertas", + "create": "Crear Alerta", + "noAlerts": "No se encontraron alertas", + "severity": { + "low": "Baja", + "medium": "Media", + "high": "Alta", + "critical": "Crítica" + } + }, + "feedback": { + "title": "Comentarios", + "list": "Lista de Comentarios", + "provide": "Proporcionar Comentarios", + "noFeedback": "No se encontraron comentarios", + "submit": "Enviar Comentarios" + }, + "settings": { + "title": "Configuración", + "description": "Administra tu cuenta y configuración de aplicación.", + "account": { + "title": "Configuración de Cuenta", + "description": "Personaliza la configuración de visibilidad del menú de tu cuenta.", + "menuVisibilityTitle": "Visibilidad del Menú para {accountName}", + "menuVisibilityDescription": "Elige qué elementos del menú mostrar u ocultar en la barra lateral.", + "noAccountSelected": "Ninguna cuenta seleccionada", + "settingsSaved": "Configuración de cuenta guardada exitosamente", + "settingsSaveError": "Error al guardar la configuración de cuenta", + "saving": "Guardando...", + "saveChanges": "Guardar Cambios" + }, + "user": "Configuración de Usuario", + "language": "Configuración de Idioma", + "theme": "Configuración de Tema", + "notifications": "Notificaciones", + "privacy": "Privacidad", + "security": "Seguridad", + "defaultLanguage": "Idioma Predeterminado", + "userLanguage": "Idioma del Usuario", + "browserLanguage": "Usar Idioma del Navegador" + }, + "documentation": { + "title": "Documentación", + "concepts": "Conceptos", + "methods": "Métodos", + "basics": "Fundamentos", + "advanced": "Avanzado", + "evaluationMetrics": "Métricas de Evaluación", + "examples": "Ejemplos", + "getStarted": "Comenzar", + "quickStart": "Inicio Rápido", + "api": "Referencia de API", + "sdk": "SDK", + "cli": "CLI", + "mcpServer": "Servidor MCP", + "workerNodes": "Nodos de Trabajo", + "universalCode": "Código Universal" + }, + "solutions": { + "title": "Soluciones", + "callCenterQA": "QA de Centro de Llamadas", + "enterprise": "Empresa", + "optimizerAgents": "Agentes Optimizadores", + "platform": "Plataforma", + "resources": "Recursos" + }, + "errors": { + "general": "Ocurrió un error", + "notFound": "No encontrado", + "unauthorized": "No autorizado", + "forbidden": "Prohibido", + "serverError": "Error del servidor", + "networkError": "Error de red", + "validationError": "Error de validación", + "required": "Este campo es obligatorio", + "invalidFormat": "Formato inválido", + "tooShort": "Demasiado corto", + "tooLong": "Demasiado largo" + }, + "success": { + "saved": "Guardado exitosamente", + "created": "Creado exitosamente", + "updated": "Actualizado exitosamente", + "deleted": "Eliminado exitosamente", + "uploaded": "Subido exitosamente", + "exported": "Exportado exitosamente", + "imported": "Importado exitosamente" + }, + "metrics": { + "accuracy": "Precisión", + "precision": "Exactitud", + "recall": "Recuperación", + "f1Score": "Puntuación F1", + "agreement": "Acuerdo", + "classImbalance": "Desequilibrio de Clases", + "classNumber": "Número de Clases", + "gauges": "Medidores", + "beforeAfter": "Antes/Después", + "distribution": "Distribución", + "confusion": "Matriz de Confusión" + }, + "languages": { + "en": "English", + "es": "Español" + } +} \ No newline at end of file diff --git a/dashboard/middleware.ts b/dashboard/middleware.ts new file mode 100644 index 000000000..23f0ec0c4 --- /dev/null +++ b/dashboard/middleware.ts @@ -0,0 +1,28 @@ +import createMiddleware from 'next-intl/middleware'; +import {locales} from './i18n'; + +export default createMiddleware({ + // A list of all locales that are supported + locales, + + // Used when no locale matches + defaultLocale: process.env.DEFAULT_LOCALE || 'en', + + // The locale detection strategy + localeDetection: true, + + // Prefix the default locale in the URL + localePrefix: 'as-needed' +}); + +export const config = { + // Match only internationalized pathnames + matcher: [ + // Enable a redirect to a matching locale at the root + '/', + + // Set a cookie to remember the locale for pages that don't have + // internationalized pathnames + '/((?!api|_next|_vercel|.*\\..*).*)' + ] +}; \ No newline at end of file diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index b90c65325..250241041 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -70,6 +70,7 @@ "lucide-react": "^0.447.0", "monaco-editor": "^0.52.2", "next": "^14.2.29", + "next-intl": "^4.1.0", "next-themes": "^0.3.0", "node-fetch": "^2.7.0", "package.json": "^0.0.0", @@ -30637,6 +30638,66 @@ "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", + "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.1", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", + "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz", + "integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/icu-skeleton-parser": "1.8.14", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz", + "integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.7.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", @@ -35947,6 +36008,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -45016,7 +45083,6 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", - "dev": true, "license": "MIT" }, "node_modules/decimal.js-light": { @@ -48979,6 +49045,18 @@ "node": ">= 0.10" } }, + "node_modules/intl-messageformat": { + "version": "10.7.16", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz", + "integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.2", + "tslib": "^2.8.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -53837,6 +53915,42 @@ } } }, + "node_modules/next-intl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.1.0.tgz", + "integrity": "sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/intl-localematcher": "^0.5.4", + "negotiator": "^1.0.0", + "use-intl": "^4.1.0" + }, + "peerDependencies": { + "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0", + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/next-intl/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next-themes": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", @@ -60798,6 +60912,20 @@ } } }, + "node_modules/use-intl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.1.0.tgz", + "integrity": "sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", + "@schummar/icu-type-parser": "1.21.5", + "intl-messageformat": "^10.5.14" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index a10ef642e..ebd0362bd 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -89,6 +89,7 @@ "lucide-react": "^0.447.0", "monaco-editor": "^0.52.2", "next": "^14.2.29", + "next-intl": "^4.1.0", "next-themes": "^0.3.0", "node-fetch": "^2.7.0", "package.json": "^0.0.0", diff --git a/dashboard/utils/format-time-i18n.ts b/dashboard/utils/format-time-i18n.ts new file mode 100644 index 000000000..325c532be --- /dev/null +++ b/dashboard/utils/format-time-i18n.ts @@ -0,0 +1,66 @@ +// Internationalized time formatting utility +export function formatTimeAgoI18n( + timestamp: string | Date, + t: (key: string, variables?: Record) => string +): string { + try { + // Handle string timestamps + const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp + + // Check for invalid dates + if (isNaN(date.getTime())) { + return t('time.invalidDate') + } + + const now = new Date(); + const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + // Handle future dates + if (diffInSeconds < 0) { + return t('time.inTheFuture'); + } + + // Less than 1 minute + if (diffInSeconds < 60) { + return t('time.lessThanMinute'); + } + + // Minutes + const diffInMinutes = Math.floor(diffInSeconds / 60); + if (diffInMinutes < 60) { + return t(diffInMinutes === 1 ? 'time.minuteAgo' : 'time.minutesAgo', { count: diffInMinutes }); + } + + // Hours + const diffInHours = Math.floor(diffInMinutes / 60); + if (diffInHours < 24) { + return t(diffInHours === 1 ? 'time.hourAgo' : 'time.hoursAgo', { count: diffInHours }); + } + + // Days + const diffInDays = Math.floor(diffInHours / 24); + if (diffInDays < 7) { + return t(diffInDays === 1 ? 'time.dayAgo' : 'time.daysAgo', { count: diffInDays }); + } + + // Weeks + const diffInWeeks = Math.floor(diffInDays / 7); + if (diffInWeeks < 4) { + return t(diffInWeeks === 1 ? 'time.weekAgo' : 'time.weeksAgo', { count: diffInWeeks }); + } + + // Months + const diffInMonths = Math.floor(diffInDays / 30); + if (diffInMonths < 12) { + return t(diffInMonths === 1 ? 'time.monthAgo' : 'time.monthsAgo', { count: diffInMonths }); + } + + // Years + const diffInYears = Math.floor(diffInDays / 365); + return t(diffInYears === 1 ? 'time.yearAgo' : 'time.yearsAgo', { count: diffInYears }); + + } catch (error) { + console.error('Error formatting date:', error) + return t('time.invalidDate') + } +} \ No newline at end of file diff --git a/dashboard/utils/format-time.ts b/dashboard/utils/format-time.ts index 1553b6e46..c39735c87 100644 --- a/dashboard/utils/format-time.ts +++ b/dashboard/utils/format-time.ts @@ -1,6 +1,7 @@ import { formatDistanceToNow } from 'date-fns' -export function formatTimeAgo(timestamp: string | Date, abbreviated: boolean = true): string { +// Simple time formatting function that returns translation keys +export function formatTimeAgo(timestamp: string | Date, abbreviated: boolean = true, locale?: string): string { try { // Handle string timestamps const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp From 6a5a503174a690799ac89ed8df769931e70c2d6e Mon Sep 17 00:00:00 2001 From: Ryan Alyn Porter Date: Wed, 18 Jun 2025 07:27:17 -0700 Subject: [PATCH 2/3] Add localization and translation strategy document for multi-language support, outlining core principles, current implementation, translation status, workflow, and brand glossary. --- LOCALIZATION.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 LOCALIZATION.md diff --git a/LOCALIZATION.md b/LOCALIZATION.md new file mode 100644 index 000000000..92f5d72c9 --- /dev/null +++ b/LOCALIZATION.md @@ -0,0 +1,38 @@ +# Localization & Translation Strategy + +This document outlines the formal plan for handling localization and translation within the Plexus project. The goal is to ensure a consistent, high-quality, multi-language user experience. + +## Core Principles + +1. **English as Source of Truth**: The English message file (`dashboard/messages/en.json`) is the canonical source for all text in the application. All new text and changes must start here. +2. **Automated Translation**: To ensure efficiency, translations for other languages will be automatically generated from the English source file. +3. **Brand Consistency**: A "Brand Glossary" is maintained to govern the translation of specific terms, ensuring that brand names, trademarks, and key concepts are handled consistently across all languages. + +## Current Implementation + +- **Technology**: The dashboard utilizes the [`next-intl`](https://next-intl.dev/) library, integrated with the Next.js App Router. +- **Message Files**: Translation messages are stored as JSON files in the `dashboard/messages/` directory. +- **Routing**: Internationalized routing is handled via a `[locale]` dynamic segment in the `dashboard/app/` directory. + +## Translation Status + +- **English (`en`)**: Complete. This is the source of truth. +- **Spanish (`es`)**: Draft translations exist in `dashboard/messages/es.json`. These were generated before the formal glossary was established and have **not been proofread**. They should not be considered final and are subject to change based on the rules defined in this document. + +## Translation Workflow + +1. **Adding New Text**: All new user-facing text must be added as a key-value pair to `dashboard/messages/en.json`. +2. **Automatic Generation**: The localization pipeline will automatically detect changes in `en.json` and generate corresponding draft translations for all other supported languages. +3. **Applying Glossary Rules**: The pipeline will consult the Brand Glossary to apply specific rules, such as preventing the translation of certain words or enforcing specific translations. +4. **Proofreading**: Generated translations must be proofread by a native speaker before they are considered ready for production. + +## Brand Glossary + +This glossary defines how specific terms should be handled by the translation process. + +| Term | Language | Instruction | Translation | Notes | +| :------ | :------- | :------------------ | :---------- | :--------------------------------------------------------------------------------------------------------------------- | +| `Plexus` | `All` | Do not translate | `Plexus` | This is a brand name. | +| `item` | `es` | Force translation | `ítem` | The Spanish word "ítem" is preferred for consistency, as advised by our Spanish language consultants. The accent is important. | + + \ No newline at end of file From 6d2cd43e26214eca20bd993f55995f1672dc9305 Mon Sep 17 00:00:00 2001 From: Osledy Bazo Date: Wed, 25 Jun 2025 18:19:27 +0200 Subject: [PATCH 3/3] Revise ES --- .../components/documentation-layout.tsx | 2 +- .../concepts/evaluations/page.tsx | 18 ++-- .../documentation/concepts/items/page.tsx | 46 ++++----- .../[locale]/documentation/concepts/page.tsx | 6 +- .../documentation/concepts/reports/page.tsx | 7 +- .../concepts/score-results/page.tsx | 16 ++-- .../documentation/concepts/scores/page.tsx | 40 ++++---- .../documentation/concepts/tasks/page.tsx | 46 ++++----- .../gauges/class-imbalance/page.tsx | 4 +- .../gauges/precision/page.tsx | 8 +- .../evaluation-metrics/gauges/recall/page.tsx | 4 +- dashboard/app/[locale]/lab/settings/page.tsx | 8 +- .../ClassDistributionVisualizer.tsx | 45 +++++---- dashboard/components/EvaluationTask.tsx | 14 ++- .../components/EvaluationTaskScoreResults.tsx | 5 +- .../PredictedClassDistributionVisualizer.tsx | 11 ++- .../ReportConfigurationSelector.tsx | 12 ++- dashboard/components/blocks/TopicAnalysis.tsx | 46 +++++---- dashboard/components/confusion-matrix.tsx | 11 ++- dashboard/components/dashboard-layout.tsx | 91 ++++++++---------- dashboard/components/items-dashboard.tsx | 2 + dashboard/components/items/ItemCard.tsx | 12 +-- .../report-configurations-dashboard.tsx | 6 +- dashboard/components/reports-dashboard.tsx | 26 ++--- .../components/ui/progress-bar-timing.tsx | 19 ++-- dashboard/messages/en.json | 64 +++++++++++-- dashboard/messages/es.json | 96 ++++++++++++------- 27 files changed, 379 insertions(+), 286 deletions(-) diff --git a/dashboard/app/[locale]/documentation/components/documentation-layout.tsx b/dashboard/app/[locale]/documentation/components/documentation-layout.tsx index 1637127d0..68026adc4 100644 --- a/dashboard/app/[locale]/documentation/components/documentation-layout.tsx +++ b/dashboard/app/[locale]/documentation/components/documentation-layout.tsx @@ -91,7 +91,7 @@ const getDocSections = (locale: string): DocSidebarItem[] => { name: "Conceptos", href: `${localePrefix}/documentation/concepts`, items: [ - { name: "Elementos", href: `${localePrefix}/documentation/concepts/items` }, + { name: "Items", href: `${localePrefix}/documentation/concepts/items` }, { name: "Fuentes", href: `${localePrefix}/documentation/concepts/sources` }, { name: "Puntuaciones", href: `${localePrefix}/documentation/concepts/scores` }, { name: "Cuadros", href: `${localePrefix}/documentation/concepts/scorecards` }, diff --git a/dashboard/app/[locale]/documentation/concepts/evaluations/page.tsx b/dashboard/app/[locale]/documentation/concepts/evaluations/page.tsx index eb1c5ad7f..e5aa7ebb0 100644 --- a/dashboard/app/[locale]/documentation/concepts/evaluations/page.tsx +++ b/dashboard/app/[locale]/documentation/concepts/evaluations/page.tsx @@ -77,8 +77,8 @@ export default function EvaluationsPage() {

Evaluaciones

- Las Evaluaciones en Plexus son cómo validas y evalúas tus cuadros de puntuación para asegurar que se alineen con tus - políticas y necesidades de partes interesadas. Te ayudan a medir la efectividad y precisión de tus criterios de puntuación + Las Evaluaciones en Plexus son cómo validas y evalúas tus cuadros de puntuación para asegurar que se alineen con tus + políticas y necesidades de partes interesadas. Te ayudan a medir la efectividad y precisión de tus criterios de puntuación antes de desplegarlos a producción.

@@ -86,8 +86,8 @@ export default function EvaluationsPage() {

¿Qué son las Evaluaciones?

- Una evaluación es como un proceso de evaluación de aprendizaje automático - es cómo pruebas y validas tus - cuadros de puntuación contra respuestas correctas conocidas. Esto ayuda a asegurar que tus criterios de puntuación estén calibrados apropiadamente + Una evaluación es como un proceso de evaluación de aprendizaje automático - es cómo pruebas y validas tus + cuadros de puntuación contra respuestas correctas conocidas. Esto ayuda a asegurar que tus criterios de puntuación estén calibrados apropiadamente y producirán resultados confiables cuando se desplieguen.

@@ -121,7 +121,7 @@ export default function EvaluationsPage() { El proceso de evaluación típicamente sigue estos pasos:

    -
  1. Seleccionar Datos: Elegir un conjunto representativo de elementos con respuestas conocidas
  2. +
  3. Seleccionar Datos: Elegir un conjunto representativo de items con respuestas conocidas
  4. Ejecutar Puntuación: Aplicar el cuadro de puntuación a los datos de prueba
  5. Comparar Resultados: Comparar las predicciones del cuadro con las respuestas correctas
  6. Analizar Métricas: Revisar precisión, recuperación y otras métricas de rendimiento
  7. @@ -171,8 +171,8 @@ export default function EvaluationsPage() {

    Evaluations

    - Evaluations in Plexus are how you validate and assess your scorecards to ensure they align with your - policies and stakeholder needs. They help you measure the effectiveness and accuracy of your scoring criteria + Evaluations in Plexus are how you validate and assess your scorecards to ensure they align with your + policies and stakeholder needs. They help you measure the effectiveness and accuracy of your scoring criteria before deploying them to production.

    @@ -180,8 +180,8 @@ export default function EvaluationsPage() {

    What are Evaluations?

    - An evaluation is like a machine learning evaluation process - it's how you test and validate your - scorecards against known correct answers. This helps ensure your scoring criteria are properly calibrated + An evaluation is like a machine learning evaluation process - it's how you test and validate your + scorecards against known correct answers. This helps ensure your scoring criteria are properly calibrated and will produce reliable results when deployed.

    diff --git a/dashboard/app/[locale]/documentation/concepts/items/page.tsx b/dashboard/app/[locale]/documentation/concepts/items/page.tsx index 48ad75ea3..95da1575f 100644 --- a/dashboard/app/[locale]/documentation/concepts/items/page.tsx +++ b/dashboard/app/[locale]/documentation/concepts/items/page.tsx @@ -4,20 +4,20 @@ import { useTranslationContext } from '@/app/contexts/TranslationContext' export default function ItemsPage() { const { locale } = useTranslationContext(); - + if (locale === 'es') { return (
    -

    Elementos

    +

    Items

    - Aprende sobre los Elementos, las unidades de contenido principales que Plexus analiza y califica. + Aprende sobre los Items, las unidades de contenido principales que Plexus analiza y califica.

    -

    ¿Qué son los Elementos?

    +

    ¿Qué son los Items?

    - Los Elementos son piezas individuales de contenido que deseas analizar o evaluar usando Plexus. + Los Items son piezas individuales de contenido que deseas analizar o evaluar usando Plexus. Pueden ser cualquier tipo de contenido que tus técnicas de puntuación de IA, ML o lógicas puedan procesar, como:

      @@ -30,15 +30,15 @@ export default function ItemsPage() {
    -

    Cómo Funcionan los Elementos

    +

    Cómo Funcionan los Items

    - Los Elementos son la base del sistema de evaluación de Plexus: + Los Items son la base del sistema de evaluación de Plexus:

    1. Organización

    - Cada Elemento pertenece a una Cuenta y puede ser referenciado por múltiples Cuadros de Puntuación. + Cada Elemento pertenece a una Cuenta y puede ser referenciado por múltiples Cuadros de Puntuación. Esto te permite evaluar el mismo contenido usando diferentes criterios o métodos de puntuación.

    @@ -53,7 +53,7 @@ export default function ItemsPage() {

    3. Evaluación

    - Los Elementos pueden ser parte de Evaluaciones, donde sus resultados de puntuación se comparan contra + Los Items pueden ser parte de Evaluaciones, donde sus resultados de puntuación se comparan contra respuestas correctas conocidas para medir la precisión y efectividad de tus métodos de puntuación.

    @@ -61,23 +61,23 @@ export default function ItemsPage() {
    -

    Propiedades del Elemento

    +

    Propiedades del Item

    Propiedades Principales

      -
    • Nombre: Un identificador único para el Elemento
    • -
    • Descripción: Detalles opcionales sobre el contenido o propósito del Elemento
    • -
    • Cuenta: La Cuenta que posee este Elemento
    • +
    • Nombre: Un identificador único para el Item
    • +
    • Descripción: Detalles opcionales sobre el contenido o propósito del Item
    • +
    • Cuenta: La Cuenta que posee este Item

    Relaciones

      -
    • Cuadros de Puntuación: Cuadros de Puntuación que referencian este Elemento
    • -
    • Trabajos de Puntuación: Registros de operaciones de puntuación realizadas en este Elemento
    • +
    • Cuadros de Puntuación: Cuadros de Puntuación que referencian este Item
    • +
    • Trabajos de Puntuación: Registros de operaciones de puntuación realizadas en este Item
    • Resultados de Puntuación: Resultados de operaciones de puntuación
    • -
    • Evaluación: Enlace opcional a una Evaluación de la que este Elemento forma parte
    • +
    • Evaluación: Enlace opcional a una Evaluación de la que este Item forma parte
    @@ -86,10 +86,10 @@ export default function ItemsPage() {

    Mejores Prácticas

      -
    • Usa nombres claros y descriptivos para tus Elementos para hacerlos fáciles de identificar
    • +
    • Usa nombres claros y descriptivos para tus Items para hacerlos fáciles de identificar
    • Incluye metadatos relevantes en la descripción para proporcionar contexto
    • -
    • Organiza los Elementos lógicamente dentro de tu estructura de Cuenta
    • -
    • Mantén un registro de qué Elementos se usan en Evaluaciones para control de calidad
    • +
    • Organiza los Items lógicamente dentro de tu estructura de Cuenta
    • +
    • Mantén un registro de qué Items se usan en Evaluaciones para control de calidad
    • Revisa regularmente los Resultados de Puntuación para monitorear la efectividad de la puntuación
    @@ -97,10 +97,10 @@ export default function ItemsPage() {

    Próximos Pasos

    - Ahora que entiendes los Elementos, puedes: + Ahora que entiendes los Items, puedes:

      -
    • Crear Cuadros de Puntuación para evaluar tus Elementos
    • +
    • Crear Cuadros de Puntuación para evaluar tus Items
    • Configurar criterios de puntuación usando Puntuaciones
    • Ejecutar Evaluaciones para medir la precisión de la puntuación
    • Monitorear resultados a través del panel de control
    • @@ -123,7 +123,7 @@ export default function ItemsPage() {

      What are Items?

      - Items are individual pieces of content that you want to analyze or evaluate using Plexus. + Items are individual pieces of content that you want to analyze or evaluate using Plexus. They can be any type of content that your AI, ML, or logical scoring techniques can process, such as:

        @@ -144,7 +144,7 @@ export default function ItemsPage() {

        1. Organization

        - Each Item belongs to an Account and can be referenced by multiple Scorecards. + Each Item belongs to an Account and can be referenced by multiple Scorecards. This allows you to evaluate the same content using different criteria or scoring methods.

        diff --git a/dashboard/app/[locale]/documentation/concepts/page.tsx b/dashboard/app/[locale]/documentation/concepts/page.tsx index 4241f9814..701ce6c6f 100644 --- a/dashboard/app/[locale]/documentation/concepts/page.tsx +++ b/dashboard/app/[locale]/documentation/concepts/page.tsx @@ -20,12 +20,12 @@ export default function BasicsPage() {

        Conceptos Principales

        -

        Elementos

        +

        Items

        - Piezas individuales de contenido que deseas analizar o evaluar usando Plexus. Los elementos son las unidades centrales que se califican. + Piezas individuales de contenido que deseas analizar o evaluar usando Plexus. Los items son las unidades centrales que se califican.

        - Aprender sobre Elementos + Aprender sobre Items
        diff --git a/dashboard/app/[locale]/documentation/concepts/reports/page.tsx b/dashboard/app/[locale]/documentation/concepts/reports/page.tsx index e63d957b8..de50de21b 100644 --- a/dashboard/app/[locale]/documentation/concepts/reports/page.tsx +++ b/dashboard/app/[locale]/documentation/concepts/reports/page.tsx @@ -2,11 +2,12 @@ import { Button as DocButton } from "@/components/ui/button" import Link from "next/link" -import { useTranslationContext } from '@/app/contexts/TranslationContext' +import { useTranslationContext, useTranslations } from '@/app/contexts/TranslationContext' export default function ReportsPage() { + const t = useTranslations('reports'); const { locale } = useTranslationContext(); - + if (locale === 'es') { return (
        @@ -100,7 +101,7 @@ export default function ReportsPage() { return (
        -

        Reports

        +

        {t('title')}

        Plexus Reports offer a powerful and flexible way to define, generate, and view custom analyses, summaries, and visualizations based on your Plexus data. Instead of building bespoke dashboards for every need, the reporting system provides reusable components and a standardized workflow.

        diff --git a/dashboard/app/[locale]/documentation/concepts/score-results/page.tsx b/dashboard/app/[locale]/documentation/concepts/score-results/page.tsx index bc177ae52..82759cf97 100644 --- a/dashboard/app/[locale]/documentation/concepts/score-results/page.tsx +++ b/dashboard/app/[locale]/documentation/concepts/score-results/page.tsx @@ -6,21 +6,21 @@ import { useTranslationContext } from '@/app/contexts/TranslationContext' export default function ScoreResultsPage() { const { locale } = useTranslationContext(); - + if (locale === 'es') { return (

        Resultados de Puntuación

        - Los Resultados de Puntuación registran los resultados de evaluar elementos contra puntuaciones en un cuadro de puntuación, proporcionando información detallada sobre el proceso de evaluación y los resultados. + Los Resultados de Puntuación registran los resultados de evaluar items contra puntuaciones en un cuadro de puntuación, proporcionando información detallada sobre el proceso de evaluación y los resultados.

        ¿Qué son los Resultados de Puntuación?

        - Un Resultado de Puntuación es un registro creado cuando un elemento es evaluado contra una puntuación o puntuaciones en un cuadro de puntuación. - Captura no solo el resultado de la evaluación sino también información contextual importante sobre cómo + Un Resultado de Puntuación es un registro creado cuando un elemento es evaluado contra una puntuación o puntuaciones en un cuadro de puntuación. + Captura no solo el resultado de la evaluación sino también información contextual importante sobre cómo se realizó la evaluación y qué datos se utilizaron.

        @@ -48,7 +48,7 @@ export default function ScoreResultsPage() {
      • Cuándo: Marcas de tiempo para la creación y última actualización
      • Cómo: Qué versión de la puntuación se utilizó
      • Por qué: Explicación del razonamiento (cuando esté disponible)
      • -
      • Contexto: Enlaces a elementos, cuadros de puntuación y evaluaciones relacionados
      • +
      • Contexto: Enlaces a items, cuadros de puntuación y evaluaciones relacionados
    @@ -80,7 +80,7 @@ export default function ScoreResultsPage() { - Elementos - Lo que se está evaluando + Items - Lo que se está evaluando @@ -106,8 +106,8 @@ export default function ScoreResultsPage() {

    What are Score Results?

    - A Score Result is a record created when an item is evaluated against a score or scores in a scorecard. - It captures not only the outcome of the evaluation but also important contextual information about how + A Score Result is a record created when an item is evaluated against a score or scores in a scorecard. + It captures not only the outcome of the evaluation but also important contextual information about how the evaluation was performed and what data was used.

    diff --git a/dashboard/app/[locale]/documentation/concepts/scores/page.tsx b/dashboard/app/[locale]/documentation/concepts/scores/page.tsx index a7a618787..f25cd010f 100644 --- a/dashboard/app/[locale]/documentation/concepts/scores/page.tsx +++ b/dashboard/app/[locale]/documentation/concepts/scores/page.tsx @@ -6,13 +6,13 @@ import { useTranslationContext } from '@/app/contexts/TranslationContext' export default function ScoresPage() { const { locale } = useTranslationContext(); - + if (locale === 'es') { return (

    Puntuaciones

    - Las puntuaciones son los elementos fundamentales de evaluación en Plexus. Definen qué quieres medir + Las puntuaciones son los items fundamentales de evaluación en Plexus. Definen qué quieres medir o evaluar sobre tu contenido.

    @@ -20,8 +20,8 @@ export default function ScoresPage() {

    ¿Qué son las Puntuaciones?

    - Las puntuaciones son criterios de evaluación individuales que definen aspectos específicos que quieres evaluar en tu contenido. - Aunque pueden ser preguntas, no se limitan solo a preguntas - pueden ser cualquier tipo de punto de evaluación + Las puntuaciones son criterios de evaluación individuales que definen aspectos específicos que quieres evaluar en tu contenido. + Aunque pueden ser preguntas, no se limitan solo a preguntas - pueden ser cualquier tipo de punto de evaluación que ayude a analizar tu contenido.

    @@ -39,7 +39,7 @@ export default function ScoresPage() { Análisis de Sentimiento: Evaluar si el tono del contenido es positivo, negativo o neutral
  8. - Verificaciones de Cumplimiento: Verificar si elementos requeridos específicos están presentes + Verificaciones de Cumplimiento: Verificar si items requeridos específicos están presentes
  9. Métricas: Medidas cuantitativas como tiempo de respuesta o conteo de palabras @@ -64,7 +64,7 @@ export default function ScoresPage() {

    Organización de las Puntuaciones

    - Las puntuaciones se organizan dentro de Cuadros de Puntuación, que agrupan criterios de evaluación relacionados. + Las puntuaciones se organizan dentro de Cuadros de Puntuación, que agrupan criterios de evaluación relacionados. Esta organización ayuda a mantener tus evaluaciones estructuradas y manejables.

    @@ -101,7 +101,7 @@ export default function ScoresPage() {

    Scores

    - Scores are the fundamental building blocks of evaluation in Plexus. They define what you want to measure + Scores are the fundamental building blocks of evaluation in Plexus. They define what you want to measure or assess about your content.

    @@ -109,8 +109,8 @@ export default function ScoresPage() {

    What are Scores?

    - Scores are individual evaluation criteria that define specific aspects you want to assess in your content. - While they can be questions, they're not limited to just questions - they can be any type of evaluation + Scores are individual evaluation criteria that define specific aspects you want to assess in your content. + While they can be questions, they're not limited to just questions - they can be any type of evaluation point that helps analyze your content.

    @@ -162,7 +162,7 @@ export default function ScoresPage() {

    Score Results

    - Plexus standardizes all score results around a common structure, ensuring consistency and enabling + Plexus standardizes all score results around a common structure, ensuring consistency and enabling powerful analysis capabilities across different types of evaluations.

    @@ -174,17 +174,17 @@ export default function ScoresPage() {

    • - Result Value: The actual outcome of the evaluation (e.g., "yes"/"no", a numeric score, + Result Value: The actual outcome of the evaluation (e.g., "yes"/"no", a numeric score, or a category)
    • - Explanation: A detailed description of why this result was chosen, providing - transparency into the decision-making process. For LLM-based scores, this often includes chain-of-thought + Explanation: A detailed description of why this result was chosen, providing + transparency into the decision-making process. For LLM-based scores, this often includes chain-of-thought reasoning similar to what you might see from models like OpenAI's GPT-4 o1/o3, Google's "thinking" models, or Deepseek R1.
    • - Confidence Level: For applicable scores (like machine learning classifiers or LLM-based - evaluations), this indicates how certain the system is about the result. This can be used for filtering, + Confidence Level: For applicable scores (like machine learning classifiers or LLM-based + evaluations), this indicates how certain the system is about the result. This can be used for filtering, quality control, or triggering human review when needed.
    @@ -216,10 +216,10 @@ export default function ScoresPage() {

    Using Scores

    - Scores are organized into scorecards, which group related evaluation criteria together. When you run an + Scores are organized into scorecards, which group related evaluation criteria together. When you run an evaluation, each score in the scorecard is applied to your content, building a comprehensive assessment.

    - +

    Score Versions

    @@ -236,7 +236,7 @@ export default function ScoresPage() { Featured Versions: Versions that are highlighted for importance or reference
  10. - Configuration: Each version contains its own configuration, including prompts, + Configuration: Each version contains its own configuration, including prompts, parameters, and other settings
  11. @@ -249,12 +249,12 @@ export default function ScoresPage() {

    - This command displays up to 10 versions in reverse chronological order (newest first), showing which + This command displays up to 10 versions in reverse chronological order (newest first), showing which version is the champion and which versions are featured.

- +
Learn about Scorecards diff --git a/dashboard/app/[locale]/documentation/concepts/tasks/page.tsx b/dashboard/app/[locale]/documentation/concepts/tasks/page.tsx index 3935ff417..ce75dff31 100644 --- a/dashboard/app/[locale]/documentation/concepts/tasks/page.tsx +++ b/dashboard/app/[locale]/documentation/concepts/tasks/page.tsx @@ -20,9 +20,9 @@ export default function TasksPage() {

¿Qué son las Tareas?

- Las tareas son la columna vertebral de Plexus, sirviendo como la infraestructura que conecta todas las - operaciones y miembros del equipo de tu organización. En su núcleo, las tareas representan comandos que operan en los recursos básicos - de Plexus—Elementos, Fuentes, Cuadros de Puntuación y Evaluaciones—y el sistema de gestión de tareas es cómo estos + Las tareas son la columna vertebral de Plexus, sirviendo como la infraestructura que conecta todas las + operaciones y miembros del equipo de tu organización. En su núcleo, las tareas representan comandos que operan en los recursos básicos + de Items, Fuentes, Cuadros de Puntuación y Evaluaciones—y el sistema de gestión de tareas es cómo estos comandos se distribuyen a computadoras trabajadoras para procesamiento.

@@ -51,7 +51,7 @@ export default function TasksPage() {

  • Tareas de Evaluación: Ejecutar evaluaciones de rendimiento en cuadros de puntuación
  • -
  • Tareas de Puntuación: Procesar elementos individuales contra criterios de puntuación
  • +
  • Tareas de Puntuación: Procesar items individuales contra criterios de puntuación
  • Tareas de Reportes: Generar reportes y análisis de datos
  • Tareas de Procesamiento de Datos: Manejar importación y transformación de datos
@@ -111,9 +111,9 @@ export default function TasksPage() {

What are Tasks?

- Tasks are the backbone of Plexus, serving as the infrastructure that connects all of your organization's - operations and team members. At their core, tasks represent commands that operate on Plexus's basic - resources—Items, Sources, Scorecards, and Evaluations—and the task management system is how these + Tasks are the backbone of Plexus, serving as the infrastructure that connects all of your organization's + operations and team members. At their core, tasks represent commands that operate on Plexus's basic + resources—Items, Sources, Scorecards, and Evaluations—and the task management system is how these commands get distributed to worker computers for processing.

@@ -129,15 +129,15 @@ export default function TasksPage() {

  • - Command Line Users: Technical team members can use Plexus's command-line tools + Command Line Users: Technical team members can use Plexus's command-line tools directly, working with YAML configurations and advanced features
  • - Worker Daemon Users: Organizations can run Plexus worker daemons that plug into + Worker Daemon Users: Organizations can run Plexus worker daemons that plug into the task dispatch system, allowing tasks to be managed through the dashboard
  • - Dashboard Users: Team members can initiate and monitor tasks entirely through + Dashboard Users: Team members can initiate and monitor tasks entirely through the web interface, without needing technical expertise
@@ -146,21 +146,21 @@ export default function TasksPage() {

Common Infrastructure for All

- While team members might use different tools based on their roles and expertise, everyone operates + While team members might use different tools based on their roles and expertise, everyone operates on the same underlying infrastructure:

  • - Shared Terminology: Whether using the dashboard or command line, everyone uses + Shared Terminology: Whether using the dashboard or command line, everyone uses the same concepts of Items, Sources, Scorecards, and Evaluations
  • - Unified Business Process: Data scientists working with data sources, ML engineers - fine-tuning models, and account representatives coordinating with clients all connect through the + Unified Business Process: Data scientists working with data sources, ML engineers + fine-tuning models, and account representatives coordinating with clients all connect through the same task system
  • - Seamless Collaboration: Technical consultants can set up advanced configurations + Seamless Collaboration: Technical consultants can set up advanced configurations that non-technical team members can then operate through the dashboard
@@ -169,9 +169,9 @@ export default function TasksPage() {

Connecting Your Organization

- The task system serves as the coordination layer that brings together team members with different skills - and responsibilities. Whether someone is creating specialized ML models, managing client relationships, - or analyzing problem domains, the task system ensures everyone can work together effectively while + The task system serves as the coordination layer that brings together team members with different skills + and responsibilities. Whether someone is creating specialized ML models, managing client relationships, + or analyzing problem domains, the task system ensures everyone can work together effectively while using the tools that best suit their needs.

@@ -219,7 +219,7 @@ export default function TasksPage() {

- Each worker advertises its capabilities (like having a GPU or access to certain data), + Each worker advertises its capabilities (like having a GPU or access to certain data), allowing Plexus to route tasks to the most appropriate worker.

@@ -274,28 +274,28 @@ export default function TasksPage() {

1. Creation

- Tasks are created when you initiate operations through the dashboard. Each task is assigned a unique ID + Tasks are created when you initiate operations through the dashboard. Each task is assigned a unique ID and configured based on your requirements.

2. Queuing

- Tasks are intelligently queued and distributed to specialized worker nodes. Our system matches tasks + Tasks are intelligently queued and distributed to specialized worker nodes. Our system matches tasks with workers that have the right capabilities (like GPU access for intensive operations).

3. Execution

- Worker nodes process tasks and provide continuous updates. You can monitor progress in real-time + Worker nodes process tasks and provide continuous updates. You can monitor progress in real-time through the dashboard, with detailed status information at each stage.

4. Completion

- When processing finishes, tasks are marked as complete and their results are stored securely. + When processing finishes, tasks are marked as complete and their results are stored securely. You can access these results through the dashboard for review and analysis.

diff --git a/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/class-imbalance/page.tsx b/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/class-imbalance/page.tsx index 6a8397c37..01839ac3f 100644 --- a/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/class-imbalance/page.tsx +++ b/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/class-imbalance/page.tsx @@ -45,13 +45,13 @@ const AccuracyGauge = ({ value, title, segments }: { export default function ClassImbalanceProblemPage() { const { locale } = useTranslationContext(); - + if (locale === 'es') { return (

El Desafío: Interpretando la Precisión con Datos Desbalanceados

- Podrías estar aquí porque una evaluación en Plexus resaltó un desbalance de clases en tu conjunto de datos. Esta es una situación común donde algunas categorías (o clases) de datos son mucho más frecuentes que otras. Por ejemplo, en un conjunto de datos de emails, los emails "normales" podrían superar vastamente en número a los emails "spam". O, en manufactura, los elementos no defectuosos podrían ser mucho más comunes que los defectuosos. + Podrías estar aquí porque una evaluación en Plexus resaltó un desbalance de clases en tu conjunto de datos. Esta es una situación común donde algunas categorías (o clases) de datos son mucho más frecuentes que otras. Por ejemplo, en un conjunto de datos de emails, los emails "normales" podrían superar vastamente en número a los emails "spam". O, en manufactura, los items no defectuosos podrían ser mucho más comunes que los defectuosos.

Aunque tener datos desbalanceados no es un error en sí mismo, puede hacer que los puntajes de precisión tradicionales sean altamente engañosos. Exploremos por qué el desbalance de clases es un factor crítico en entender el verdadero rendimiento de tu clasificador y cómo interpretar correctamente las métricas de evaluación en estos escenarios. diff --git a/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/precision/page.tsx b/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/precision/page.tsx index 25ccbd493..e0b851b62 100644 --- a/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/precision/page.tsx +++ b/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/precision/page.tsx @@ -64,13 +64,13 @@ const alwaysProhibitedEmailData = { export default function PrecisionGaugePage() { const { locale } = useTranslationContext(); - + if (locale === 'es') { return (

El Indicador de Precisión de Plexus

- La precisión es una métrica clave que responde la pregunta: "De todos los elementos que el clasificador etiquetó como positivos, ¿qué proporción eran realmente positivos?" Mide la exactitud o corrección de las predicciones positivas. Un puntaje de precisión alto indica que el clasificador tiene una tasa baja de Falsos Positivos (FP). + La precisión es una métrica clave que responde la pregunta: "De todos los items que el clasificador etiquetó como positivos, ¿qué proporción eran realmente positivos?" Mide la exactitud o corrección de las predicciones positivas. Un puntaje de precisión alto indica que el clasificador tiene una tasa baja de Falsos Positivos (FP).

@@ -98,13 +98,13 @@ export default function PrecisionGaugePage() { Precisión = Verdaderos Positivos / (Verdaderos Positivos + Falsos Positivos)

- Los segmentos visuales en el Indicador de Precisión (ej., colores indicando niveles de rendimiento) típicamente representan puntos de referencia generales de rendimiento. Un puntaje de precisión del 90% se entiende generalmente como que 9 de cada 10 elementos marcados como positivos por el modelo eran realmente positivos. Mientras que el desbalance extremo de clases puede hacer que lograr alta precisión sea desafiante, la interpretación del puntaje de precisión en sí mismo es bastante directa. Los segmentos ayudan a categorizar visualmente este rendimiento (ej., pobre, regular, bueno, excelente). + Los segmentos visuales en el Indicador de Precisión (ej., colores indicando niveles de rendimiento) típicamente representan puntos de referencia generales de rendimiento. Un puntaje de precisión del 90% se entiende generalmente como que 9 de cada 10 items marcados como positivos por el modelo eran realmente positivos. Mientras que el desbalance extremo de clases puede hacer que lograr alta precisión sea desafiante, la interpretación del puntaje de precisión en sí mismo es bastante directa. Los segmentos ayudan a categorizar visualmente este rendimiento (ej., pobre, regular, bueno, excelente).

Ejemplo: Indicador de Precisión

- Una precisión del 85% indica que el 85% de los elementos predichos como positivos eran realmente positivos. + Una precisión del 85% indica que el 85% de los items predichos como positivos eran realmente positivos.

diff --git a/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/recall/page.tsx b/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/recall/page.tsx index 4d837cb8e..7ecc47c0b 100644 --- a/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/recall/page.tsx +++ b/dashboard/app/[locale]/documentation/evaluation-metrics/gauges/recall/page.tsx @@ -44,13 +44,13 @@ const precisionForProhibitedInAlwaysSafe = 0; // 0 / (0 + 0) which is undefined, export default function RecallGaugePage() { const { locale } = useTranslationContext(); - + if (locale === 'es') { return (

El Indicador de Recuperación de Plexus

- La recuperación, también conocida como Sensibilidad o Tasa de Verdaderos Positivos (TVP), responde la pregunta: "De todos los elementos que fueron realmente positivos, ¿qué proporción identificó correctamente el clasificador?" Mide la integridad o exhaustividad del clasificador para encontrar todas las instancias positivas. Un puntaje alto de recuperación indica que el clasificador tiene una tasa baja de Falsos Negativos (FN). + La recuperación, también conocida como Sensibilidad o Tasa de Verdaderos Positivos (TVP), responde la pregunta: "De todos los items que fueron realmente positivos, ¿qué proporción identificó correctamente el clasificador?" Mide la integridad o exhaustividad del clasificador para encontrar todas las instancias positivas. Un puntaje alto de recuperación indica que el clasificador tiene una tasa baja de Falsos Negativos (FN).

diff --git a/dashboard/app/[locale]/lab/settings/page.tsx b/dashboard/app/[locale]/lab/settings/page.tsx index 02d5c4a3a..d1dc46f15 100644 --- a/dashboard/app/[locale]/lab/settings/page.tsx +++ b/dashboard/app/[locale]/lab/settings/page.tsx @@ -21,14 +21,14 @@ export default function LabSettings() { {t('user')} - Customize your user preferences. + {t('customize')} -

Update your profile, change notification preferences, and manage security settings.

+

{t('userDescription')}

- Manage Menu Visibility + {t('manageVisibility')}
@@ -40,7 +40,7 @@ export default function LabSettings() { {t('account.description')} -

Configure default settings for your organization.

+

{t('organizationDescription')}

{t('account.title')} diff --git a/dashboard/components/ClassDistributionVisualizer.tsx b/dashboard/components/ClassDistributionVisualizer.tsx index 78c0902a3..04e9a2b36 100644 --- a/dashboard/components/ClassDistributionVisualizer.tsx +++ b/dashboard/components/ClassDistributionVisualizer.tsx @@ -7,19 +7,20 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" -import { - SquareSplitHorizontalIcon as SquareSplit, - Layers, - Scale, - EqualNotIcon, - SquareMinusIcon, - Info +import { + SquareSplitHorizontalIcon as SquareSplit, + Layers, + Scale, + EqualNotIcon, + SquareMinusIcon, + Info } from 'lucide-react' import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" +import { useTranslations } from '@/app/contexts/TranslationContext' export interface ClassDistribution { label: string @@ -42,28 +43,30 @@ const getSegmentPosition = (index: number, total: number) => { return 'middle' } -export default function ClassDistributionVisualizer({ - data = [], +export default function ClassDistributionVisualizer({ + data = [], rotateThreshold = 8, hideThreshold = 4, isBalanced = null, hideHeader = false, onLabelSelect }: ClassDistributionVisualizerProps) { - const safeData = Array.isArray(data) ? - [...data].sort((a, b) => b.count - a.count) : + const t = useTranslations('evaluations'); + + const safeData = Array.isArray(data) ? + [...data].sort((a, b) => b.count - a.count) : [] const classCount = safeData.length - + const hasHeader = !hideHeader && (isBalanced !== null || classCount > 0) if (!safeData || classCount === 0) { return (
-
- No data
@@ -90,27 +93,27 @@ export default function ClassDistributionVisualizer({
{classCount === 1 ? ( <> - - Labels: One class + {t('labels')}: {t('oneClass')} ) : classCount === 2 ? ( <> - - Labels: Binary + {t('labels')}: {t('binary')} ) : ( <> - Labels: {classCount} classes + {t('labels')}: {classCount} {t('classes')} - diff --git a/dashboard/components/EvaluationTask.tsx b/dashboard/components/EvaluationTask.tsx index 794f2770a..8b455af17 100644 --- a/dashboard/components/EvaluationTask.tsx +++ b/dashboard/components/EvaluationTask.tsx @@ -18,6 +18,7 @@ import { EvaluationListAccuracyBar } from '@/components/EvaluationListAccuracyBa import isEqual from 'lodash/isEqual' import { standardizeScoreResults } from '@/utils/data-operations' import { ScoreResultComponent, ScoreResultData } from '@/components/ui/score-result' +import { useTranslations } from '@/app/contexts/TranslationContext' export interface EvaluationMetric { name: string @@ -193,6 +194,9 @@ const mapTaskStatus = (status: string | undefined | null): 'PENDING' | 'RUNNING' } const getStatusMessage = (data: EvaluationTaskData) => { + const t = useTranslations('evaluations'); + const tItems = useTranslations('items'); + // If we have task data with stages, use that if (data.task?.stages?.items?.length) { // If task failed, show the failed stage's message @@ -228,22 +232,22 @@ const getStatusMessage = (data: EvaluationTaskData) => { return firstStage?.statusMessage; } } - + // Otherwise, construct a status message from the evaluation data if (data.status === 'COMPLETED') { - return `Processed ${data.processedItems} of ${data.totalItems} items`; + return `${t('processed')} ${data.processedItems} ${t('of')} ${data.totalItems} ${tItems('items')}`; } if (data.status === 'FAILED') { return data.errorMessage || 'Task failed'; } if (data.status === 'RUNNING') { - return `Processing ${data.processedItems} of ${data.totalItems} items...`; + return `${t('processing')} ${data.processedItems} ${t('of')} ${data.totalItems} ${tItems('items')}...`; } return undefined; } -const GridContent = React.memo(({ data, extra, isSelected }: { - data: EvaluationTaskData; +const GridContent = React.memo(({ data, extra, isSelected }: { + data: EvaluationTaskData; extra?: boolean; isSelected?: boolean; }) => { diff --git a/dashboard/components/EvaluationTaskScoreResults.tsx b/dashboard/components/EvaluationTaskScoreResults.tsx index 5dab6d551..9b4000f08 100644 --- a/dashboard/components/EvaluationTaskScoreResults.tsx +++ b/dashboard/components/EvaluationTaskScoreResults.tsx @@ -11,6 +11,7 @@ import { } from '@/components/ui/dropdown-menu' import type { Schema } from "@/amplify/data/resource" import { ScoreResultComponent, ScoreResultData } from '@/components/ui/score-result' +import { useTranslations } from '@/app/contexts/TranslationContext' interface FilterState { showCorrect: boolean | null // null means show all @@ -37,6 +38,8 @@ export function EvaluationTaskScoreResults({ selectedScoreResult, navigationControls }: EvaluationTaskScoreResultsProps) { + const t = useTranslations('evaluations'); + console.log('EvaluationTaskScoreResults render:', { resultCount: results.length, firstResult: results[0], @@ -165,7 +168,7 @@ export function EvaluationTaskScoreResults({
- Score Results ({filteredResults.length}) + {t('scoreResults')} ({filteredResults.length})
{navigationControls} diff --git a/dashboard/components/PredictedClassDistributionVisualizer.tsx b/dashboard/components/PredictedClassDistributionVisualizer.tsx index 4c849c1e5..fcb43effa 100644 --- a/dashboard/components/PredictedClassDistributionVisualizer.tsx +++ b/dashboard/components/PredictedClassDistributionVisualizer.tsx @@ -1,6 +1,7 @@ import React from 'react' import { ChartPie } from 'lucide-react' import ClassDistributionVisualizer, { type ClassDistribution } from './ClassDistributionVisualizer' +import { useTranslations } from '@/app/contexts/TranslationContext' interface PredictedClassDistributionVisualizerProps { data?: ClassDistribution[] @@ -9,20 +10,22 @@ interface PredictedClassDistributionVisualizerProps { onLabelSelect?: (label: string) => void } -export default function PredictedClassDistributionVisualizer({ - data = [], +export default function PredictedClassDistributionVisualizer({ + data = [], rotateThreshold = 8, hideThreshold = 4, onLabelSelect }: PredictedClassDistributionVisualizerProps) { + const t = useTranslations('evaluations'); + return (
- Predicted classes + {t('predictedClasses')}
- () @@ -20,19 +21,20 @@ async function listReportConfigurations(accountId: string): ModelListResult = ({ - selectedReportConfiguration, +const ReportConfigurationSelector: React.FC = ({ + selectedReportConfiguration, setSelectedReportConfiguration, useMockData = false }) => { + const t = useTranslations('reports'); const [reportConfigurations, setReportConfigurations] = useState>([]) const [isLoading, setIsLoading] = useState(!useMockData) const [accountId, setAccountId] = useState(null) // Debug logging useEffect(() => { - console.debug('ReportConfigurationSelector state:', { - selectedReportConfiguration, + console.debug('ReportConfigurationSelector state:', { + selectedReportConfiguration, configsCount: reportConfigurations.length, configs: reportConfigurations.map(c => ({ id: c.value, name: c.label })) }); @@ -127,7 +129,7 @@ const ReportConfigurationSelector: React.FC = - All Report Configurations + {t('allReportConfigurations')} {reportConfigurations.map(config => ( {config.label} diff --git a/dashboard/components/blocks/TopicAnalysis.tsx b/dashboard/components/blocks/TopicAnalysis.tsx index e0ee7e3a7..43b2c5962 100644 --- a/dashboard/components/blocks/TopicAnalysis.tsx +++ b/dashboard/components/blocks/TopicAnalysis.tsx @@ -8,6 +8,7 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { ChevronDown, ChevronRight, Eye, EyeOff, MessagesSquare, Microscope, FileText } from 'lucide-react'; import * as yaml from 'js-yaml'; +import { useTranslations } from '@/app/contexts/TranslationContext'; interface TopicAnalysisData { summary?: string; @@ -84,18 +85,19 @@ interface TopicAnalysisData { /** * Topic Analysis Report Block Component - * + * * Displays a comprehensive topic analysis report with sections for: * - Pre-processing - * - LLM Extraction + * - LLM Extraction * - BERTopic Analysis * - Fine-tuning */ const TopicAnalysis: React.FC = (props) => { + const t = useTranslations('common'); const [promptExpanded, setPromptExpanded] = useState(false); const [examplesExpanded, setExamplesExpanded] = useState(false); - + // Debug logging to see what we're receiving console.log('🔍 TopicAnalysis component received props:', { hasOutput: !!props.output, @@ -205,12 +207,12 @@ const TopicAnalysis: React.FC = (props) => { LLM Extraction {llmExtraction.examples && (
- {llmExtraction.examples.length} examples + {llmExtraction.examples.length} {t('examples')}
)} - ; }> = ({ topics }) => { + const t = useTranslations('topics'); + if (topics.length === 0) { return (
-

Topic Analysis Results

+

{t('topicAnalysisResults')}

- Analyzing topics... + {t('analyzingTopics')}
@@ -331,11 +335,11 @@ const TopicAnalysisResults: React.FC<{
-

Topic Analysis Results

+

{t('topicAnalysisResults')}

- {topics.length} topics discovered + {topics.length} {t('topicsDiscovered')}
- +
{topics.map((topic) => ( @@ -365,7 +369,7 @@ const TopicAnalysisResults: React.FC<{ {/* Debug: Show if examples are missing */} {(!topic.examples || topic.examples.length === 0) && (
- No examples available for this topic + {t('noExamplesAvailable')}
)}
@@ -385,18 +389,19 @@ const TopicExamplesSection: React.FC<{ examples: string[]; topicId: number; }> = ({ examples, topicId }) => { + const t = useTranslations('topics'); const [expanded, setExpanded] = useState(false); - + if (examples.length === 0) { return null; } - + return (
- @@ -527,12 +532,13 @@ const LLMExtractionSection: React.FC<{ examplesExpanded: boolean; setExamplesExpanded: (expanded: boolean) => void; }> = ({ llmExtraction, promptExpanded, setPromptExpanded, examplesExpanded, setExamplesExpanded }) => { + const t = useTranslations('common'); const [selectedExamples, setSelectedExamples] = useState>(new Set()); const [showAllExamples, setShowAllExamples] = useState(false); - + const examples = llmExtraction.examples || []; const displayedExamples = showAllExamples ? examples : examples.slice(0, 20); - + const toggleExampleSelection = (index: number) => { const newSelection = new Set(selectedExamples); if (newSelection.has(index)) { @@ -647,10 +653,10 @@ const LLMExtractionSection: React.FC<{ ); })}
- + {!showAllExamples && examples.length > 20 && (
- Showing 20 of {examples.length} examples + Showing 20 of {examples.length} {t('examples')}
)}
diff --git a/dashboard/components/confusion-matrix.tsx b/dashboard/components/confusion-matrix.tsx index 30970afbb..141188b7b 100644 --- a/dashboard/components/confusion-matrix.tsx +++ b/dashboard/components/confusion-matrix.tsx @@ -9,6 +9,7 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" +import { useTranslations } from "@/app/contexts/TranslationContext" export interface ConfusionMatrixRow { actualClassLabel: string; // e.g., "Red", "Heads" - represents the true class for this row @@ -40,21 +41,23 @@ export interface ConfusionMatrixProps { /** * ConfusionMatrix Component - * + * * Displays a confusion matrix with interactive elements: * - Clickable cells showing the count of predictions * - Tooltips with detailed information * - Row labels showing actual classes * - Column labels showing predicted classes - * + * * All interactions (cell clicks, row labels, column labels) emit a standardized - * selection event with both predicted and actual values, using null for the + * selection event with both predicted and actual values, using null for the * unselected dimension: * - Cell click: { predicted: "class1", actual: "class2" } * - Row label: { predicted: null, actual: "class2" } * - Column label: { predicted: "class1", actual: null } */ export function ConfusionMatrix({ data, onSelectionChange }: ConfusionMatrixProps) { + const t = useTranslations('evaluations'); + // Updated validation logic if (!data || !data.labels) { return ( @@ -145,7 +148,7 @@ export function ConfusionMatrix({ data, onSelectionChange }: ConfusionMatrixProp
- Confusion matrix + {t('confusionMatrix')}
diff --git a/dashboard/components/dashboard-layout.tsx b/dashboard/components/dashboard-layout.tsx index da21dd295..d025c2a94 100644 --- a/dashboard/components/dashboard-layout.tsx +++ b/dashboard/components/dashboard-layout.tsx @@ -1,16 +1,11 @@ "use client" import * as React from "react" -import { useState, useEffect, useRef } from "react" -import { Activity, StickyNote, FileBarChart, FlaskConical, ListChecks, LogOut, Menu, PanelLeft, PanelRight, Settings, Sparkles, Siren, Database, Sun, Moon, Send, Mic, Headphones, MessageCircleMore, MessageSquare, Inbox, X, ArrowLeftRight, Layers3, Monitor, CircleHelp } from "lucide-react" +import { useState, useEffect } from "react" +import { Activity, StickyNote, FileBarChart, FlaskConical, ListChecks, LogOut, Menu, PanelLeft, PanelRight, Settings, Siren, Database, Sun, Moon, Send, Mic, Headphones, MessageCircleMore, MessageSquare, X, ArrowLeftRight, Layers3, Monitor, CircleHelp } from "lucide-react" import Link from "next/link" import { usePathname } from "next/navigation" import { useTheme } from "next-themes" -import { generateClient } from "aws-amplify/data" -import { listFromModel } from "@/utils/amplify-helpers" -import type { Schema } from "@/amplify/data/resource" -import type { AccountSettings } from "@/types/account-config" -import { isValidAccountSettings } from "@/types/account-config" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Button, type ButtonProps } from "@/components/ui/button" @@ -55,11 +50,10 @@ const DashboardButton = React.forwardRef( ) DashboardButton.displayName = "DashboardButton" -const MobileHeader = ({ - toggleLeftSidebar, - toggleRightSidebar, - rightSidebarState -}: { +const MobileHeader = ({ + toggleLeftSidebar, + toggleRightSidebar, +}: { toggleLeftSidebar: () => void; toggleRightSidebar: () => void; rightSidebarState: 'collapsed' | 'normal' | 'expanded'; @@ -73,7 +67,7 @@ const MobileHeader = ({ > - + @@ -89,10 +83,6 @@ const MobileHeader = ({
) -const client = generateClient() - -type Account = Schema['Account']['type'] - export const menuItems = [ { name: "Items", icon: StickyNote, path: "/lab/items" }, { name: "Evaluations", icon: FlaskConical, path: "/lab/evaluations" }, @@ -108,6 +98,7 @@ export const menuItems = [ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; signOut: () => Promise }) => { const t = useTranslations('navigation') + const tCommon = useTranslations('common') const [isLeftSidebarOpen, setIsLeftSidebarOpen] = useState(true) const { rightSidebarState, setRightSidebarState } = useSidebar() const { theme, setTheme } = useTheme() @@ -115,7 +106,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig const isMobile = useMediaQuery("(max-width: 1023px)") const { accounts, selectedAccount, isLoadingAccounts, visibleMenuItems, setSelectedAccount } = useAccount() - useEffect(() => { + useEffect(() => { if (isDesktop) { setIsLeftSidebarOpen(true) } else if (isMobile) { @@ -140,7 +131,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig const newState = prevState === 'collapsed' ? 'normal' : 'collapsed'; return newState; }); - + // Close left sidebar when opening chat on mobile if (rightSidebarState === 'collapsed') { setIsLeftSidebarOpen(false); @@ -148,7 +139,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig } else { // Desktop behavior remains the same setRightSidebarState((prevState) => { - const newState = prevState === 'collapsed' ? 'normal' : + const newState = prevState === 'collapsed' ? 'normal' : prevState === 'normal' ? 'expanded' : 'collapsed'; console.log('Setting desktop right sidebar state:', { from: prevState, @@ -182,7 +173,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig
{visibleMenuItems.map((item) => ( - - {selectedAccount?.name?.split(' ').map(word => word[0]).join('') || 'AC'} - {isLeftSidebarOpen && {selectedAccount?.name || 'Select Account'}} + {isLeftSidebarOpen && {selectedAccount?.name || t('selectAccount')}} - Settings + {tCommon('settings')} @@ -249,25 +240,25 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - Select Account + {t('selectAccount')} {isLoadingAccounts ? ( - Loading accounts... + {t('loadingAccounts')} ) : accounts.length === 0 ? ( - No accounts found + {t('noAccountsFound')} ) : ( accounts.map((account) => ( - setSelectedAccount(account)} > - {account.name.split(' ').map(word => word[0]).join('')} @@ -296,18 +287,18 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - My Account + {t('myAccount')} - Settings + {tCommon('settings')} - Sign out + {t('signOut')} @@ -325,7 +316,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - {isLeftSidebarOpen ? "Toggle sidebar" : "Expand sidebar"} + {isLeftSidebarOpen ? t('toggleSidebar') : t('expandSidebar')} @@ -349,7 +340,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - Toggle {theme === "dark" ? "Light" : theme === "light" ? "System" : "Dark"} Mode + {theme === "dark" ? t('toggleLightMode') : theme === "light" ? t('toggleSystemMode') : t('toggleDarkMode')} @@ -378,7 +369,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - Expand chat + {t('expandChat')} @@ -465,10 +456,10 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig {rightSidebarState !== 'collapsed' && (
- @@ -491,7 +482,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - Dictate Message + {t('dictateMessage')} @@ -507,7 +498,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - Voice Mode + {t('voiceMode')} @@ -526,7 +517,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig - {rightSidebarState === 'collapsed' ? "Expand chat" : "Collapse chat"} + {rightSidebarState === 'collapsed' ? t('expandChat') : t('collapseChat')} @@ -536,8 +527,8 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig } const toggleTheme = () => { - setTheme(theme === "dark" ? "light" : - theme === "light" ? "system" : + setTheme(theme === "dark" ? "light" : + theme === "light" ? "system" : "dark") } @@ -607,7 +598,7 @@ const DashboardLayout = ({ children, signOut }: { children: React.ReactNode; sig `}> {isMobile && rightSidebarState !== 'collapsed' && (
- Chat + {t('chat')} { + const t = useTranslations('items'); + // Only show "No items found" if we're not loading and have actually finished the initial load if (filteredItems.length === 0 && !isLoading && hasInitiallyLoaded) { return ( diff --git a/dashboard/components/items/ItemCard.tsx b/dashboard/components/items/ItemCard.tsx index fb776d54b..149f1c6bf 100644 --- a/dashboard/components/items/ItemCard.tsx +++ b/dashboard/components/items/ItemCard.tsx @@ -164,8 +164,8 @@ const ItemCard = React.forwardRef(({ // Grid mode content const renderGridContent = () => ( -
- + (({
- {hasMultipleScorecards ? - `${item.scorecards.length} ${t('scorecards')}` : + {hasMultipleScorecards ? + `${item.scorecards.length} ${t('scorecards')}` : item.scorecards[0]?.scorecardName || t('scorecard') }
)} - + {/* Results count - only show if there are actually scorecards */} {item.scorecards.length > 0 && (
- {t(totalResults === 1 ? 'scoreResult' : 'scoreResults')} + {t(totalResults === 1 ? 'scoreResults' : 'scoreResults')}
)} diff --git a/dashboard/components/report-configurations-dashboard.tsx b/dashboard/components/report-configurations-dashboard.tsx index 30de44255..d6c82fda9 100644 --- a/dashboard/components/report-configurations-dashboard.tsx +++ b/dashboard/components/report-configurations-dashboard.tsx @@ -10,6 +10,7 @@ import { getClient } from '@/utils/amplify-client' import type { GraphQLResult } from '@aws-amplify/api' import { toast } from "sonner" import { useAuthenticator } from '@aws-amplify/ui-react' +import { useTranslations } from "@/app/contexts/TranslationContext" // Define types based on Amplify schema type ReportConfiguration = { @@ -66,6 +67,7 @@ const CREATE_REPORT_CONFIGURATION = ` const ACCOUNT_KEY = process.env.NEXT_PUBLIC_PLEXUS_ACCOUNT_KEY || 'call-criteria' export function ReportConfigurationsDashboard() { + const t = useTranslations('reports'); const { user } = useAuthenticator() const router = useRouter() const [configurations, setConfigurations] = useState([]) @@ -209,7 +211,7 @@ export function ReportConfigurationsDashboard() { if (error) { return (
-

Report Configurations

+

{t('reportConfigurations')}

{error}
@@ -231,7 +233,7 @@ export function ReportConfigurationsDashboard() {
-

Report Configurations

+

{t('reportConfigurations')}

{error &&

{error}

}
diff --git a/dashboard/components/reports-dashboard.tsx b/dashboard/components/reports-dashboard.tsx index ec09e7072..add3cbd6c 100644 --- a/dashboard/components/reports-dashboard.tsx +++ b/dashboard/components/reports-dashboard.tsx @@ -526,11 +526,13 @@ export default function ReportsDashboard({ }: { initialSelectedReportId?: string | null, } = {}) { + const t = useTranslations('reports'); + // Add a useEffect to potentially log when setup runs relative to mount useEffect(() => { console.log("ReportsDashboard mounted. Block registry setup should have run."); }, []); - const tReports = useTranslations('reports'); + const tReports = useTranslations('reports'); const { user } = useAuthenticator() const router = useRouter() @@ -733,13 +735,13 @@ export default function ReportsDashboard({ if (data?.onCreateReport) { const newReport = data.onCreateReport; console.log('🔔 SUB-EVENT: Original new report name:', newReport.name, 'type:', typeof newReport.name); - + // DIRECT APPROACH: Create a report display object manually to ensure name is preserved const manualTransformedReport: ReportDisplayData = { id: newReport.id, - name: typeof newReport.name === 'string' ? newReport.name : - (typeof newReport.name === 'object' && newReport.name !== null) ? - (newReport.name as any).name || `Report ${newReport.id.substring(0, 6)}` : + name: typeof newReport.name === 'string' ? newReport.name : + (typeof newReport.name === 'object' && newReport.name !== null) ? + (newReport.name as any).name || `Report ${newReport.id.substring(0, 6)}` : `Report ${newReport.id.substring(0, 6)}`, createdAt: newReport.createdAt, updatedAt: newReport.updatedAt, @@ -1301,7 +1303,7 @@ export default function ReportsDashboard({ if (error && !dataHasLoadedOnce) { // Show error prominently if initial load failed return (
-

Reports

+

{t('title')}

Error fetching reports: {error}
@@ -1346,7 +1348,7 @@ export default function ReportsDashboard({ >
{!isLoading && reports.length === 0 && !error && ( -
No reports found for this account.
+
{t('noReports')}
)} {reports.length > 0 && (
{ reportRefsMap.current.set(report.id, el); @@ -1469,7 +1471,7 @@ export default function ReportsDashboard({ {/* Loading indicator */} {isLoadingMore && (
-
Loading more reports...
+
{t('loadingMoreReports')}
)}
diff --git a/dashboard/components/ui/progress-bar-timing.tsx b/dashboard/components/ui/progress-bar-timing.tsx index 48e906187..c31d6948a 100644 --- a/dashboard/components/ui/progress-bar-timing.tsx +++ b/dashboard/components/ui/progress-bar-timing.tsx @@ -2,6 +2,7 @@ import React from "react" import { cn } from "@/lib/utils" import { Timer, AlarmClock, AlarmClockCheck } from "lucide-react" import { Timestamp } from "./timestamp" +import { useTranslations } from "@/app/contexts/TranslationContext" export interface ProgressBarTimingProps { elapsedTime?: string @@ -22,10 +23,12 @@ export function ProgressBarTiming({ startedAt, completedAt }: ProgressBarTimingProps) { + const t = useTranslations('evaluations'); + return (
- {elapsedTime ? ( - Elapsed: {elapsedTime} + {t('elapsed')}: {elapsedTime} ) : startedAt ? (
- Elapsed: - {t('elapsed')}: +
) : ( - Elapsed: 0s + {t('elapsed')}: 0s )}
{estimatedTimeRemaining && ( diff --git a/dashboard/messages/en.json b/dashboard/messages/en.json index df3bb5b58..36c46885b 100644 --- a/dashboard/messages/en.json +++ b/dashboard/messages/en.json @@ -1,7 +1,7 @@ { "navigation": { "items": "Items", - "evaluations": "Evaluations", + "evaluations": "Evaluations", "reports": "Reports", "scorecards": "Scorecards", "sources": "Sources", @@ -10,15 +10,32 @@ "activity": "Activity", "feedback": "Feedback", "alerts": "Alerts", - "help": "Help" + "help": "Help", + "account": "Account", + "selectAccount": "Select Account", + "loadingAccounts": "Loading accounts...", + "noAccountsFound": "No accounts found", + "myAccount": "My Account", + "signOut": "Sign out", + "toggleSidebar": "Toggle sidebar", + "expandSidebar": "Expand sidebar", + "toggleLightMode": "Toggle Light Mode", + "toggleSystemMode": "Toggle System Mode", + "toggleDarkMode": "Toggle Dark Mode", + "expandChat": "Expand chat", + "collapseChat": "Collapse chat", + "dictateMessage": "Dictate Message", + "voiceMode": "Voice Mode", + "typeMessage": "Type a message...", + "chat": "Chat" }, "time": { "lessThanMinute": "<1 minute ago", "minuteAgo": "{count} minute ago", - "minutesAgo": "{count} minutes ago", + "minutesAgo": "{count} minutes ago", "hourAgo": "{count} hour ago", "hoursAgo": "{count} hours ago", - "dayAgo": "{count} day ago", + "dayAgo": "{count} day ago", "daysAgo": "{count} days ago", "weekAgo": "{count} week ago", "weeksAgo": "{count} weeks ago", @@ -87,7 +104,8 @@ "next": "Next", "previous": "Previous", "yes": "Yes", - "no": "No" + "no": "No", + "examples": "Examples" }, "dashboard": { "title": "Dashboard", @@ -120,7 +138,17 @@ "delete": "Delete", "evaluateAccuracy": "Evaluate Accuracy", "evaluateConsistency": "Evaluate Consistency", - "evaluateAlignment": "Evaluate Alignment" + "evaluateAlignment": "Evaluate Alignment", + "processed": "Processed", + "of": "of", + "processing": "Processing", + "elapsed": "Elapsed", + "confusionMatrix": "Confusion Matrix", + "predictedClasses": "Predicted Classes", + "labels": "Labels", + "oneClass": "One class", + "binary": "Binary", + "classes": "classes" }, "scorecards": { "title": "Scorecards", @@ -149,6 +177,7 @@ }, "items": { "title": "Items", + "items": "Items", "item": "Item", "itemDetails": "Item Details", "list": "Item List", @@ -188,7 +217,10 @@ "reportGenerated": "Report Generated", "reportFailed": "Report Generation Failed", "runReport": "Run Report", - "editConfigurations": "Edit Configurations" + "editConfigurations": "Edit Configurations", + "allReportConfigurations": "All Report Configurations", + "reportConfigurations": "Report Configurations", + "loadingMoreReports": "Loading more reports..." }, "datasets": { "title": "Datasets", @@ -248,9 +280,18 @@ "noFeedback": "No feedback found", "submit": "Submit Feedback" }, + "topics": { + "topicAnalysisResults": "Topic Analysis Results", + "analyzingTopics": "Analyzing topics...", + "examples": "Examples", + "example": "Example", + "topicsDiscovered": "topics discovered", + "noExamplesAvailable": "No examples available for this topic" + }, "settings": { "title": "Settings", "description": "Manage your account and application settings.", + "customize": "Customize your user preferences.", "account": { "title": "Account Settings", "description": "Customize your account menu visibility settings.", @@ -263,6 +304,9 @@ "saveChanges": "Save Changes" }, "user": "User Settings", + "userDescription": "Update your profile, change notification preferences, and manage security settings.", + "manageVisibility": "Manage Menu Visibility", + "organizationDescription": "Configure default settings for your organization.", "language": "Language Settings", "theme": "Theme Settings", "notifications": "Notifications", @@ -286,7 +330,7 @@ "sdk": "SDK", "cli": "CLI", "mcpServer": "MCP Server", - "workerNodes": "Worker Nodes", + "workerNodes": "Worker Nodes", "universalCode": "Universal Code" }, "solutions": { @@ -301,7 +345,7 @@ "general": "An error occurred", "notFound": "Not found", "unauthorized": "Unauthorized", - "forbidden": "Forbidden", + "forbidden": "Forbidden", "serverError": "Server error", "networkError": "Network error", "validationError": "Validation error", @@ -312,7 +356,7 @@ }, "success": { "saved": "Saved successfully", - "created": "Created successfully", + "created": "Created successfully", "updated": "Updated successfully", "deleted": "Deleted successfully", "uploaded": "Uploaded successfully", diff --git a/dashboard/messages/es.json b/dashboard/messages/es.json index 9eee097f9..e3d80291a 100644 --- a/dashboard/messages/es.json +++ b/dashboard/messages/es.json @@ -1,7 +1,7 @@ { "navigation": { - "items": "Elementos", - "evaluations": "Evaluaciones", + "items": "Items", + "evaluations": "Evaluaciones", "reports": "Reportes", "scorecards": "Cuadros", "sources": "Fuentes", @@ -10,15 +10,34 @@ "activity": "Actividad", "feedback": "Comentarios", "alerts": "Alertas", - "help": "Ayuda" + "help": "Ayuda", + "settings": "Configuración", + "account": "Cuenta", + "selectAccount": "Seleccionar Cuenta", + "loadingAccounts": "Cargando cuentas...", + "noAccountsFound": "No se encontraron cuentas", + "myAccount": "Mi Cuenta", + "signOut": "Cerrar Sesión", + "toggleSidebar": "Alternar barra lateral", + "expandSidebar": "Expandir barra lateral", + "toggleLightMode": "Alternar Modo Claro", + "toggleSystemMode": "Alternar Modo Sistema", + "toggleDarkMode": "Alternar Modo Oscuro", + "expandChat": "Expandir chat", + "collapseChat": "Contraer chat", + "dictateMessage": "Dictar Mensaje", + "voiceMode": "Modo Voz", + "typeMessage": "Escribe un mensaje...", + "chat": "Chat", + "examples": "Ejemplos" }, "time": { "lessThanMinute": "hace <1 minuto", "minuteAgo": "hace {count} minuto", - "minutesAgo": "hace {count} minutos", + "minutesAgo": "hace {count} minutos", "hourAgo": "hace {count} hora", "hoursAgo": "hace {count} horas", - "dayAgo": "hace {count} día", + "dayAgo": "hace {count} día", "daysAgo": "hace {count} días", "weekAgo": "hace {count} semana", "weeksAgo": "hace {count} semanas", @@ -89,27 +108,6 @@ "yes": "Sí", "no": "No" }, - "navigation": { - "dashboard": "Panel de Control", - "evaluations": "Evaluaciones", - "scorecards": "Cuadros", - "items": "Elementos", - "reports": "Informes", - "datasets": "Conjuntos de Datos", - "batches": "Lotes", - "tasks": "Tareas", - "activity": "Actividad", - "alerts": "Alertas", - "feedback": "Comentarios", - "feedbackQueues": "Colas de Comentarios", - "settings": "Configuración", - "documentation": "Documentación", - "sources": "Fuentes", - "help": "Ayuda", - "lab": "Laboratorio", - "platform": "Plataforma", - "solutions": "Soluciones" - }, "dashboard": { "title": "Panel de Control", "welcome": "Bienvenido a Plexus", @@ -141,7 +139,17 @@ "delete": "Eliminar", "evaluateAccuracy": "Evaluar Precisión", "evaluateConsistency": "Evaluar Consistencia", - "evaluateAlignment": "Evaluar Alineación" + "evaluateAlignment": "Evaluar Alineación", + "processed": "Procesados", + "processing": "Procesando", + "of": "de", + "elapsed": "Transcurrido", + "confusionMatrix": "Matriz de Confusión", + "predictedClasses": "Clases Predichas", + "labels": "Etiquetas", + "oneClass": "Una clase", + "binary": "Binaria", + "classes": "clases" }, "scorecards": { "title": "Cuadros", @@ -169,19 +177,20 @@ "confidence": "confianza" }, "items": { - "title": "Elementos", - "item": "Elemento", - "itemDetails": "Detalles del Elemento", - "list": "Lista de Elementos", - "details": "Detalles del Elemento", + "title": "Items", + "items": "Items", + "item": "Item", + "itemDetails": "Detalles del Item", + "list": "Lista de Items", + "details": "Detalles del Item", "metadata": "Metadatos", "content": "Contenido", - "noItems": "No se encontraron elementos", + "noItems": "No se encontraron items", "fileAttachments": "Archivos Adjuntos", "scoreResults": "Resultados de Puntuación", "searchByIdentifier": "Buscar por identificador", "search": "Buscar", - "loadingMoreItems": "Cargando más elementos...", + "loadingMoreItems": "Cargando más items...", "scoreResultDetails": "Detalles del Resultado de Puntuación", "description": "Descripción", "text": "Texto", @@ -209,7 +218,10 @@ "reportGenerated": "Informe Generado", "reportFailed": "Generación de Informe Fallida", "runReport": "Ejecutar Informe", - "editConfigurations": "Editar Configuraciones" + "editConfigurations": "Editar Configuraciones", + "allReportConfigurations": "Todas las Configuraciones de Informe", + "reportConfigurations": "Configuraciones de Informe", + "loadingMoreReports": "Cargando más informes..." }, "datasets": { "title": "Conjuntos de Datos", @@ -269,14 +281,23 @@ "noFeedback": "No se encontraron comentarios", "submit": "Enviar Comentarios" }, + "topics": { + "topicAnalysisResults": "Resultados del Análisis de Tópicos", + "analyzingTopics": "Analizando tópicos...", + "examples": "Ejemplos", + "example": "Ejemplo", + "topicsDiscovered": "tópicos descubiertos", + "noExamplesAvailable": "No hay ejemplos disponibles para este tópico" + }, "settings": { "title": "Configuración", "description": "Administra tu cuenta y configuración de aplicación.", + "customize": "Personaliza tus preferencias de usuario.", "account": { "title": "Configuración de Cuenta", "description": "Personaliza la configuración de visibilidad del menú de tu cuenta.", "menuVisibilityTitle": "Visibilidad del Menú para {accountName}", - "menuVisibilityDescription": "Elige qué elementos del menú mostrar u ocultar en la barra lateral.", + "menuVisibilityDescription": "Elige qué items del menú mostrar u ocultar en la barra lateral.", "noAccountSelected": "Ninguna cuenta seleccionada", "settingsSaved": "Configuración de cuenta guardada exitosamente", "settingsSaveError": "Error al guardar la configuración de cuenta", @@ -284,6 +305,9 @@ "saveChanges": "Guardar Cambios" }, "user": "Configuración de Usuario", + "userDescription": "Actualiza tu perfil, cambia las preferencias de notificación y administra la configuración de seguridad.", + "manageVisibility": "Administrar Visibilidad del Menú", + "organizationDescription": "Configura la configuración predeterminada para tu organización.", "language": "Configuración de Idioma", "theme": "Configuración de Tema", "notifications": "Notificaciones",