Cuándo Usarlo
Usa este skill al diseñar, construir o modificar widgets de interfaz de usuario interactivos que muestren contenido React personalizado dentro de clientes de IA o NitroStudio.
1. Definición en Backend (@Widget)
Para mostrar un widget basado en React para la salida de una herramienta, decora el método de la herramienta con @Widget.
Opciones:
- Ruta de cadena: Una cadena simple que representa el identificador de la ruta en la aplicación React frontend (ej.
'product-card'). - Ruta de objeto: Objeto que incluye:
route(obligatorio): La ruta.domain(opcional): Dominio de sandbox permitido.csp(opcional): Directrices de Política de Seguridad de Contenido.
Ejemplo:
import { Tool, Widget, z } from '@nitrostack/core';
export class CatalogTools {
@Tool({
name: 'fetch_product',
description: 'Obtén información del producto por código de barras.',
inputSchema: z.object({ barcode: z.string() }),
})
@Widget('product-details') // Se asigna al componente frontend "product-details"
async fetchProduct(input: { barcode: string }) {
return {
name: 'Super Nitro Energy Drink',
price: 2.99,
sku: input.barcode,
};
}
}
2. Widget React en Frontend (@nitrostack/widgets)
En tu aplicación frontend de widget React (normalmente un componente cliente Next.js), usa el hook useWidgetSDK para recibir datos de entrada del host cliente.
Ejemplo de Componente React:
'use client';
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
interface ProductData {
name: string;
price: number;
sku: string;
}
export default function ProductDetailsWidget() {
const { isReady, getToolOutput, theme } = useWidgetSDK();
const data = getToolOutput<ProductData>();
if (!isReady) {
return <div className="loading">Conectando al host...</div>;
}
if (!data) {
return <div className="error">No se recibieron datos del producto.</div>;
}
return (
<div className={`product-card ${theme === 'dark' ? 'dark' : 'light'}`}>
<h3>{data.name}</h3>
<p className="price">${data.price.toFixed(2)}</p>
<span className="sku">SKU: {data.sku}</span>
</div>
);
}
3. Gestión y Sincronización de Estado (useWidgetState)
Usa useWidgetState para gestionar y persistir el estado del widget del lado del cliente (ej. pestañas seleccionadas, valores de filtro, estados de entrada). Este estado se sincroniza automáticamente con el contexto de la aplicación host, persistiendo a través de los re-renderizados de la página.
Ejemplo:
import React from 'react';
import { useWidgetState } from '@nitrostack/widgets';
export default function StationPanelWidget() {
const [state, setState] = useWidgetState(() => ({
selectedTab: 'overview',
showExtendedInfo: false,
}));
return (
<div>
<button onClick={() => setState({ ...state, selectedTab: 'alerts' })}>
Ver Alertas
</button>
<p>Pestaña Actual: {state?.selectedTab}</p>
</div>
);
}
4. Invocar Herramientas del Núcleo desde Widgets (callTool)
Puedes invocar otras herramientas MCP del backend directamente desde el widget frontend usando callTool. Esto es útil para encadenar herramientas o desencadenar auditorías detalladas.
Ejemplo:
import React, { useState } from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function SystemDiagnostics() {
const { callTool, isReady } = useWidgetSDK();
const [isRunning, setIsRunning] = useState(false);
const [result, setResult] = useState<string | null>(null);
const runDiagnostic = async () => {
if (!isReady) return;
setIsRunning(true);
try {
const response = await callTool('run_diagnostic', { system: 'oxygen_scrubber' });
setResult(response.result as string);
} catch (err) {
setResult('La ejecución del diagnóstico falló.');
} finally {
setIsRunning(false);
}
};
return (
<button onClick={runDiagnostic} disabled={isRunning}>
{isRunning ? 'Ejecutando...' : 'Ejecutar Diagnóstico'}
</button>
);
}
5. Control de Diseño y Visualización
Los widgets pueden solicitar dinámicamente cambios en el modo de tamaño (pantalla completa, en línea, picture-in-picture) y adaptar los diseños a las áreas seguras (islas dinámicas/notch) o restricciones de altura máxima.
Métodos Clave:
requestFullscreen(): Cambia la visualización del widget host a pantalla completa.requestInline(): Cambia la visualización del widget host de nuevo a en línea.requestPip(): Flota el widget en modo Picture-in-Picture.requestClose(): Descarta el widget por completo.
Ejemplo:
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function StatusBoard() {
const {
requestFullscreen,
requestInline,
requestClose,
displayMode, // Propiedad reactiva ('fullscreen' | 'inline' | 'pip')
maxHeight, // Restricción reactiva de maxHeight (en píxeles)
getSafeArea // Datos de márgenes: { top, right, bottom, left }
} = useWidgetSDK();
const safeArea = getSafeArea() || { top: 0, bottom: 0 };
return (
<div style={{ maxHeight: maxHeight || 400, paddingTop: safeArea.top }}>
<h3>Modo: {displayMode}</h3>
<button onClick={requestFullscreen}>Pantalla Completa</button>
<button onClick={requestInline}>Colapsar</button>
<button onClick={requestClose}>Descartar Widget</button>
</div>
);
}
6. Navegación y Acciones de Chat
Los widgets pueden interactuar con el panel de chat del host usando enlaces externos del navegador y mensajes de seguimiento.
Métodos Clave:
openExternal(url): Abre la URL de destino de forma segura en el navegador externo principal del usuario.sendFollowUpMessage(prompt): Inserta un mensaje en el flujo de chat, enviándolo automáticamente al agente LLM.
Ejemplo:
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function MissionControl() {
const { openExternal, sendFollowUpMessage } = useWidgetSDK();
return (
<div>
{/* Abrir documentación externa */}
<button onClick={() => openExternal('https://docs.nitrostack.dev')}>
Abrir Manual
</button>
{/* Preguntar directamente al agente LLM desde el widget */}
<button onClick={() => sendFollowUpMessage('Analiza las fallos de misión de la semana pasada')}>
Preguntar al Agente
</button>
</div>
);
}
7. Consultas de Medios y Accesibilidad
El SDK proporciona utilidades auxiliares para consultar las capacidades del cliente objetivo para estilos o accesibilidad.
Utilidades Clave:
prefersReducedMotion(): Devuelvetruesi la configuración del cliente especifica movimiento reducido. Desactiva animaciones.isPrimarilyTouchDevice(): Devuelvetruesi el dispositivo tiene un puntero grueso (ej. táctil/móvil). Aumenta el tamaño de los botones.isHoverAvailable(): Devuelvetruesi el puntero admite estados de hover.prefersDarkColorScheme(): Devuelvetruesi el tema del sistema es oscuro.
Ejemplo:
import React from 'react';
import { isPrimarilyTouchDevice, prefersReducedMotion } from '@nitrostack/widgets';
export default function AccessiblePanel() {
const isTouch = isPrimarilyTouchDevice();
const reducedMotion = prefersReducedMotion();
return (
<div className={reducedMotion ? 'no-animations' : 'smooth-transitions'}>
<button style={{ padding: isTouch ? '16px' : '8px' }}>
Botón Interactivo
</button>
</div>
);
}
8. Pruebas de Widgets
- Abre tu proyecto en NitroStudio para una vista previa visual.
- Invoca la herramienta desde el chat de IA o el panel de pruebas para verificar que el widget se actualice instantáneamente con la estructura JSON devuelta.