Permitir que las integraciones declaren sus propios widgets de panel (esquema JSON)

La necesidad

Hoy en día, los widgets del panel de control se desarrollan todos en el núcleo de Gladys. Una integración (especialmente externa, a través del SDK) no puede ofrecer un widget específico para su dominio, aunque es allí donde los datos adquieren todo su significado: seguimiento de la producción solar, estado de un robot aspirador, programación de carga de un coche eléctrico, etc.

La propuesta

Permitir que una integración declare uno o varios widgets mediante un esquema JSON, siguiendo el mismo principio que las páginas de configuración de las integraciones externas. La integración describe qué mostrar (valores, gráficos, botones, estados, indicadores…), y es Gladys quien decide cómo mostrarlo.

Intencionalmente, no hay HTML personalizado ni iframe: es una elección filosófica. Al mantenerse declarativo, el núcleo de Gladys garantiza para todos los widgets, incluidos los de terceros: la coherencia visual, el modo oscuro, la adaptabilidad móvil/tableta, las traducciones, el rendimiento y la no regresión cuando la interfaz evoluciona. Es el mismo modelo que los widgets de iOS: puramente declarativos, y nadie encuentra que el ecosistema carece de riqueza :slightly_smiling_face:

Los beneficios

  • Las integraciones se vuelven completas: sus datos finalmente tienen un lugar real en el panel de control
  • La experiencia sigue siendo limpia y unificada, sin importar el autor del widget
  • El JSON es validable, por lo que una IA podrá generar o reparar estos widgets de manera fiable (y, a largo plazo, construir un panel de control completo bajo demanda)
  • El vocabulario de componentes puede enriquecerse progresivamente en el núcleo, sin romper nunca nada, y cada adición beneficia a todas las integraciones de una vez

Líneas de reflexión

  • Definir el vocabulario inicial: ¿qué componentes básicos? (valor + unidad, gráfico histórico, botón de acción, lista de estados…)
  • ¿Cómo recupera el widget sus datos: características de dispositivos existentes o endpoint expuesto por la integración?
  • Versionar el esquema para que los widgets sigan siendo compatibles con las actualizaciones

¡Muy muy buena idea! Parece al principio de las aplicaciones de Android que pueden proponer widgets :+1:

¡Hola a todos!

Este tema ahora está en desarrollo.

Se ha abierto una PR para especificar los widgets de panel declarados por las integraciones (esquema JSON):

No duden en seguir la PR, probar (opcional, especialmente para pequeñas solicitudes) y dar sus comentarios aquí si es necesario.

¡Hola a todos! :waving_hand:

El desarrollo está listo para ser probado, y me encantaría recibir los comentarios de varios desarrolladores de integraciones para verificar que la API responde a los casos de uso reales antes del lanzamiento.

Aquí tienes un avance del resultado:

La PR contiene la especificación de la API (también puedes dársela a Claude para desarrollar una integración mientras tanto): https://github.com/GladysAssistant/Gladys/pull/3109

La imagen Docker para probar:

ghcr.io/gladysassistant/gladys-preview:claude-exciting-babbage-waa7zm

@spenceur ¿Podrías volver a hacer tu integración de cine como una integración externa con esta API y decirme si responde a tus necesidades? :slightly_smiling_face:

El calendario es bastante ajustado, así que estoy abierto a todos sus comentarios lo antes posible. ¡El objetivo es publicarlo el lunes si todo va bien! :rocket:

Voy a probar esto este fin de semana, pero por lo que veo en las capturas de pantalla, me gusta :+1:

Arfffff gracias @pierre-gilles no estaré muy disponible hasta el domingo :sweat_smile::sweat_smile: lo miraré el domingo por la noche (si puede esperar hasta entonces ?)

Ps: lo miraré esta noche en cuanto pueda, he visto el final del mensaje :smiling_face_with_tear:

@pierre-gilles
Widget :



Disparador


Caso en que el plugin se bloquea :


el disparador sigue siendo mostrado pero gladys funciona correctamente :slight_smile:

Todo es funcional.
¿Podrías añadir una función de filtro de tipo « contains » no sensible a mayúsculas/minúsculas en los disparadores :sweat_smile:? :smiley:

¡Excelente, gracias por la prueba! Los videos quedan genial :grin:

¡Se puede añadir!

Tengo un problema con ciertas imágenes que exceden el límite de tamaño que Gladys impone

Si te puede ayudar, aquí está lo que dice Claude:
Detalle técnico:

  • El núcleo limita las imágenes de los widgets a 300 Ko (MAX_WIDGET_IMAGE_BYTES = 300 * 1024 en externalIntegration.normalizeWidgetImage.js), un límite voluntario para evitar que un widget cargue una imagen enorme en el navegador.
  • Verifiqué recuperando los pósteres directamente desde el CDN de AlloCiné (all.web.img.acsta.net): los que se muestran bien tienen 182-241 Ko, los dos rotos tienen 341 Ko y 378 Ko — por encima del límite.
  • Cuando onWidgetGetImage devuelve una imagen demasiado pesada, el núcleo rechaza la respuesta en el servidor y el front solo muestra el icono de imagen rota, con un mensaje de error genérico (REQUEST_TO_THIRD_PARTY_FAILED) que no dice explícitamente « demasiado pesada » — me tomó un momento rastrearlo.

No es un error en el código de la integración: widget.js hace exactamente lo mismo para todas las imágenes, es solo que el CDN de AlloCiné a veces sirve pósteres más pesados que el límite de Gladys para ciertas películas. No hay un parámetro de redimensionamiento en la URL del póster de CGR para solicitar una versión más ligera, por lo que no hay nada que corregir en la integración.

Probado con Intégration externe - Prochains passages de l'ISS :rocket:
Sin problemas, ni durante el desarrollo ni durante las pruebas (es un widget personalizado pero sin configuración)

He probado esta PR de principio a fin con un proveedor de integración falso que sirve un widget de marea hablando el protocolo WebSocket en bruto (sin Docker ni SDK — el SDK 0.13.0 aún no expone los handlers de widget). El mecanismo funciona: el widget aparece en el selector, los ajustes se validan, el contenido se normaliza, la acción de ida y vuelta y el nudge widget.refresh se comportan como se especifica. La tubería es sólida.

Luego intenté un caso concreto: el widget de marea de #3028. El resultado es un punto de datos útil, porque la diferencia no es cosmética.

Primero, el widget central de #3028; luego, lo que pude hacer:


Tres cosas son estructuralmente imposibles, y no solo menos bonitas:

  1. El reloj de marea — un dial con una aguja que indica la posición en el ciclo y el tiempo restante antes de la pleamar. No existe un componente de dial, y ni value, ni gauge, ni chart lo aproximan: una gauge es un arco radial para una proporción, no un dial con dos polos etiquetados PM/BM.

  2. Las anotaciones en la curva — los marcadores de pleamar y bajamar con su hora y altura, las etiquetas de coeficiente, el punto discontinuo del momento presente. chart solo acepta series de puntos, por lo que la curva pierde exactamente la información para la que se lee. No se mira una curva de marea por su forma, sino para leer « 07h48, 10,69 m, coef. 74 ».

  3. Las pestañas de días (hoy + 6 días). Entiendo que este sea deliberado — «un widget se lee y se toca, no es una página»— pero una marea sin el día siguiente pierde su razón de ser: se consulta una marea para preparar una salida, por lo tanto, con antelación. Una lista de tarjetas de 7 elementos haría bien un foco, pero ocuparía el espacio de la curva, que es el corazón del widget.

Esto no pone en duda. La capacidad sigue siendo la respuesta correcta para el caso piloto TMDB y para los widgets de dominio citados en la especificación. Mi punto es más estrecho: la línea de división «widgets de dominio vs widgets de control genéricos» no es suficiente para clasificar un widget como la marea. No es un widget de control, por lo que la sección «fuera de alcance» no lo excluye; pero su representación se basa en una representación específica del dominio (un dial, una curva anotada) que el vocabulario no puede describir por construcción. La especificación dice «el peor widget de terceros posible se parece a un widget central un poco cargado» — aquí, ocurre lo contrario: el mejor widget que el vocabulario permita está claramente por debajo de lo que el núcleo ya sabe hacer.

Dos sugerencias, en el espíritu aditivo del principio 7:

  • Un componente dial (valor, min, max, dos etiquetas de polos, etiqueta central) se uniría a gauge como segundo componente radial. Sirve para la marea, pero también para una rosa de los vientos, una posición en un ciclo de carga, una fase lunar…
  • Anotaciones opcionales en chart: una tabla acotada de { t, label, value, color } que el núcleo renderiza en marcadores, más un booleano now_marker. Aditivo, ignorado por un núcleo más antiguo, y útil más allá de la marea (picos de producción solar, franjas tarifarias, fin de carga previsto).

@pierre-gilles Está bien en la integración de precios de combustible:

Edición: Quería fijar el gráfico en 30 días, pero Claude me dio esta respuesta:

En los 30 días, para ser claro: el título sigue siendo «30 últimos días» y el eje se extiende hacia la izquierda con el tiempo hasta cubrir un mes. No puedo mostrar un eje de 30 días desde el primer día sin inventar precios que nunca hemos medido — el front no permite forzar un min/max de eje, y fabricar puntos sería datos falsos en un gráfico de precios. Si prefieres un eje fijo de 30 días, la única opción honesta sería un parche en el núcleo (exponer xaxis.min/max en el componente chart)

@pierre-gilles ¿Crees que es pertinente?

Para los 30 días « no completados », no me sorprende y ya es lo que ocurre con otros sensores cuando solo tienes unos pocos días de datos y los muestras en 30, el gráfico solo muestra lo que tiene y comienza a la izquierda en la primera fecha de los datos en la base.