Quando Usar
Use esta skill ao projetar, construir ou modificar widgets de interface de usuário interativos que exibem conteúdo React personalizado dentro de clientes de IA ou NitroStudio.
1. Definição no Backend (@Widget)
Para exibir um widget baseado em React para a saída de uma ferramenta, decore o método da ferramenta com @Widget.
Opções:
- Rota String: Uma string simples representando o identificador da rota no aplicativo React de frontend (ex.:
'product-card'). - Rota Objeto: Objeto incluindo:
route(obrigatório): O caminho da rota.domain(opcional): Domínio de sandbox permitido.csp(opcional): Diretrizes de Política de Segurança de Conteúdo.
Exemplo:
import { Tool, Widget, z } from '@nitrostack/core';
export class CatalogTools {
@Tool({
name: 'fetch_product',
description: 'Get product information by barcode.',
inputSchema: z.object({ barcode: z.string() }),
})
@Widget('product-details') // Mapeia para o componente frontend "product-details"
async fetchProduct(input: { barcode: string }) {
return {
name: 'Super Nitro Energy Drink',
price: 2.99,
sku: input.barcode,
};
}
}
2. Widget React no Frontend (@nitrostack/widgets)
Em seu aplicativo frontend de widget React (tipicamente um componente cliente Next.js), use o hook useWidgetSDK para receber dados de entrada do host cliente.
Exemplo 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 ao host...</div>;
}
if (!data) {
return <div className="error">Nenhum dado de produto recebido.</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. Gerenciamento e Sincronização de Estado (useWidgetState)
Use useWidgetState para gerenciar e persistir o estado do widget no lado do cliente (ex.: abas selecionadas, valores de filtro, estados de entrada). Esse estado é sincronizado automaticamente com o contexto do aplicativo host, persistindo entre re-renderizações de página.
Exemplo:
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>Aba Atual: {state?.selectedTab}</p>
</div>
);
}
4. Chamando Ferramentas Principais a partir de Widgets (callTool)
Você pode invocar outras ferramentas MCP do backend diretamente do widget frontend usando callTool. Isso é útil para encadeamento de ferramentas ou acionamento de auditorias detalhadas.
Exemplo:
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('Falha na execução do diagnóstico.');
} finally {
setIsRunning(false);
}
};
return (
<button onClick={runDiagnostic} disabled={isRunning}>
{isRunning ? 'Executando...' : 'Executar Diagnóstico'}
</button>
);
}
5. Controles de Layout e Exibição
Os widgets podem solicitar dinamicamente mudanças no modo de tamanho (tela cheia, inline, picture-in-picture) e adaptar layouts a áreas seguras (ilhas dinâmicas/notches) ou restrições de altura máxima.
Métodos Principais:
requestFullscreen(): Muda a exibição do widget host para tela cheia.requestInline(): Muda a exibição do widget host de volta para inline.requestPip(): Flutua o widget em Picture-in-Picture.requestClose(): Dispensa o widget completamente.
Exemplo:
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function StatusBoard() {
const {
requestFullscreen,
requestInline,
requestClose,
displayMode, // Propriedade reativa ('fullscreen' | 'inline' | 'pip')
maxHeight, // Restrição reativa de maxHeight (em pixels)
getSafeArea // Dados de insets: { 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}>Tela Cheia</button>
<button onClick={requestInline}>Recolher</button>
<button onClick={requestClose}>Fechar Widget</button>
</div>
);
}
6. Navegação e Ações no Chat
Os widgets podem interagir com o painel de chat do host usando links de navegador externos e prompts de acompanhamento.
Métodos Principais:
openExternal(url): Abre a URL de destino com segurança no navegador externo principal do usuário.sendFollowUpMessage(prompt): Insere uma mensagem no fluxo de chat, enviando-a automaticamente para o agente LLM.
Exemplo:
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function MissionControl() {
const { openExternal, sendFollowUpMessage } = useWidgetSDK();
return (
<div>
{/* Abrir documentação externa */}
<button onClick={() => openExternal('https://docs.nitrostack.dev')}>
Abrir Manual
</button>
{/* Perguntar ao agente LLM diretamente do widget */}
<button onClick={() => sendFollowUpMessage('Analyze mission failures from last week')}>
Pedir Análise ao Agente
</button>
</div>
);
}
7. Consultas de Mídia e Acessibilidade
O SDK fornece utilitários auxiliares para consultar as capacidades do cliente alvo para estilização ou acessibilidade.
Principais Utilitários:
prefersReducedMotion(): Retornatruese as configurações do cliente especificarem movimento reduzido. Desabilite animações.isPrimarilyTouchDevice(): Retornatruese o dispositivo tiver um ponteiro grosseiro (ex.: toque/mobile). Aumente os tamanhos alvo dos botões.isHoverAvailable(): Retornatruese o ponteiro suportar estados de hover.prefersDarkColorScheme(): Retornatruese o tema do sistema for escuro.
Exemplo:
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ão Interativo
</button>
</div>
);
}
8. Testando Widgets
- Abra seu projeto no NitroStudio para uma pré-visualização visual.
- Invoque a ferramenta a partir do chat de IA ou do painel de teste para verificar se o widget é atualizado instantaneamente com a estrutura JSON retornada.