Quand l'utiliser
Utilisez cette compétence lorsque vous concevez, construisez ou modifiez des widgets d'interface utilisateur interactifs qui affichent du contenu React personnalisé dans des clients IA ou NitroStudio.
1. Définition backend (@Widget)
Pour afficher un widget basé sur React pour la sortie d'un outil, décorez la méthode de l'outil avec @Widget.
Options :
- Route chaîne : Une simple chaîne représentant l'identifiant de la route dans l'application React frontend (ex.
'product-card'). - Route objet : Objet comprenant :
route(obligatoire) : Le chemin de la route.domain(optionnel) : Domaine de sandbox autorisé.csp(optionnel) : Directives de politique de sécurité du contenu.
Exemple :
import { Tool, Widget, z } from '@nitrostack/core';
export class CatalogTools {
@Tool({
name: 'fetch_product',
description: 'Obtenir les informations d'un produit par code-barres.',
inputSchema: z.object({ barcode: z.string() }),
})
@Widget('product-details') // Mappe au composant frontend "product-details"
async fetchProduct(input: { barcode: string }) {
return {
name: 'Super Nitro Energy Drink',
price: 2.99,
sku: input.barcode,
};
}
}
2. Widget React frontend (@nitrostack/widgets)
Dans votre application de widgets React frontend (typiquement un composant client Next.js), utilisez le hook useWidgetSDK pour recevoir les données d'entrée de l'hôte client.
Exemple de composant 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">Connexion à l'hôte...</div>;
}
if (!data) {
return <div className="error">Aucune donnée produit reçue.</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. Gestion et synchronisation d'état (useWidgetState)
Utilisez useWidgetState pour gérer et persister l'état côté client du widget (ex. onglets sélectionnés, valeurs de filtre, états de saisie). Cet état se synchronise automatiquement avec le contexte de l'application hôte, persistant à travers les re-rendus de page.
Exemple :
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' })}>
Voir les alertes
</button>
<p>Onglet actuel : {state?.selectedTab}</p>
</div>
);
}
4. Appel d'outils principaux depuis les widgets (callTool)
Vous pouvez invoquer d'autres outils MCP backend directement depuis le widget frontend à l'aide de callTool. C'est utile pour le chaînage d'outils ou le déclenchement d'audits détaillés.
Exemple :
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('Échec de l\'exécution du diagnostic.');
} finally {
setIsRunning(false);
}
};
return (
<button onClick={runDiagnostic} disabled={isRunning}>
{isRunning ? 'Exécution...' : 'Lancer le diagnostic'}
</button>
);
}
5. Contrôles de mise en page et d'affichage
Les widgets peuvent demander dynamiquement des changements de mode de taille (plein écran, incrusté, picture-in-picture) et adapter les mises en page aux zones de sécurité (dynamic islands/notches) ou aux contraintes de hauteur maximale.
Méthodes clés :
requestFullscreen(): Passe l'affichage du widget hôte en plein écran.requestInline(): Passe l'affichage du widget hôte en mode incrusté.requestPip(): Fait flotter le widget en Picture-in-Picture.requestClose(): Ferme complètement le widget.
Exemple :
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function StatusBoard() {
const {
requestFullscreen,
requestInline,
requestClose,
displayMode, // Propriété réactive ('fullscreen' | 'inline' | 'pip')
maxHeight, // Contrainte réactive de hauteur maximale (en pixels)
getSafeArea // Données d'encart : { top, right, bottom, left }
} = useWidgetSDK();
const safeArea = getSafeArea() || { top: 0, bottom: 0 };
return (
<div style={{ maxHeight: maxHeight || 400, paddingTop: safeArea.top }}>
<h3>Mode : {displayMode}</h3>
<button onClick={requestFullscreen}>Plein écran</button>
<button onClick={requestInline}>Réduire</button>
<button onClick={requestClose}>Fermer le widget</button>
</div>
);
}
6. Navigation et actions dans le chat
Les widgets peuvent interagir avec le panneau de chat hôte à l'aide de liens externes et d'invites de suivi.
Méthodes clés :
openExternal(url): Ouvre l'URL cible en toute sécurité dans le navigateur externe principal de l'utilisateur.sendFollowUpMessage(prompt): Insère un message dans le flux de chat, le soumettant automatiquement à l'agent LLM.
Exemple :
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function MissionControl() {
const { openExternal, sendFollowUpMessage } = useWidgetSDK();
return (
<div>
{/* Ouvrir la documentation externe */}
<button onClick={() => openExternal('https://docs.nitrostack.dev')}>
Ouvrir le manuel
</button>
{/* Interroger directement l'agent LLM depuis le widget */}
<button onClick={() => sendFollowUpMessage('Analyser les échecs de mission de la semaine dernière')}>
Demander à l'agent d'analyser
</button>
</div>
);
}
7. Requêtes média et d'accessibilité
Le SDK fournit des utilitaires pour interroger les capacités du client cible à des fins de style ou d'accessibilité.
Utilitaires clés :
prefersReducedMotion(): Renvoietruesi les paramètres du client spécifient un mouvement réduit. Désactiver les animations.isPrimarilyTouchDevice(): Renvoietruesi le dispositif possède un pointeur grossier (ex. tactile/mobile). Augmenter la taille des cibles des boutons.isHoverAvailable(): Renvoietruesi le pointeur prend en charge les états de survol.prefersDarkColorScheme(): Renvoietruesi le thème système est sombre.
Exemple :
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' }}>
Bouton interactif
</button>
</div>
);
}
8. Tester les widgets
- Ouvrez votre projet dans NitroStudio pour un aperçu visuel.
- Invoquez l'outil depuis le chat IA ou le panneau de test pour vérifier que le widget se met à jour instantanément avec la structure JSON renvoyée.