使用するタイミング
AIクライアントまたはNitroStudio内でカスタムReactコンテンツを表示するインタラクティブなUIウィジェットを設計、構築、または修正する場合にこのスキルを使用します。
1. バックエンド定義(@Widget)
ツールの出力にReactベースのウィジェットを表示するには、ツールメソッドを @Widget でデコレートします。
オプション:
- 文字列ルート:フロントエンドReactアプリのルート識別子を表す単純な文字列(例:
'product-card')。 - オブジェクトルート:以下を含むオブジェクト:
route(必須):ルートパス。domain(オプション):許可されるサンドボックスドメイン。csp(オプション):コンテンツセキュリティポリシーガイドライン。
例:
import { Tool, Widget, z } from '@nitrostack/core';
export class CatalogTools {
@Tool({
name: 'fetch_product',
description: 'バーコードで商品情報を取得します。',
inputSchema: z.object({ barcode: z.string() }),
})
@Widget('product-details') // 「product-details」フロントエンドコンポーネントにマッピング
async fetchProduct(input: { barcode: string }) {
return {
name: 'スーパーニトロエナジードリンク',
price: 2.99,
sku: input.barcode,
};
}
}
2. フロントエンドReactウィジェット(@nitrostack/widgets)
Reactウィジェットのフロントエンドアプリケーション(通常はNext.jsのクライアントコンポーネント)で、useWidgetSDK フックを使用してクライアントホストから入力データを受信します。
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">ホストに接続中...</div>;
}
if (!data) {
return <div className="error">商品データが受信されませんでした。</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. 状態管理と同期(useWidgetState)
useWidgetState を使用して、クライアント側のウィジェット状態(選択されたタブ、フィルター値、入力状態など)を管理・永続化します。この状態はホストアプリケーションコンテキストと自動的に同期され、ページの再レンダリングをまたいで保持されます。
例:
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' })}>
アラートを表示
</button>
<p>現在のタブ: {state?.selectedTab}</p>
</div>
);
}
4. ウィジェットからのコアツール呼び出し(callTool)
callTool を使用して、フロントエンドウィジェットから他のバックエンドMCPツールを直接呼び出すことができます。これはツールチェーンや詳細な監査のトリガーに便利です。
例:
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('診断の実行に失敗しました。');
} finally {
setIsRunning(false);
}
};
return (
<button onClick={runDiagnostic} disabled={isRunning}>
{isRunning ? '実行中...' : '診断を実行'}
</button>
);
}
5. レイアウトと表示制御
ウィジェットは動的にサイズモードの変更(フルスクリーン、インライン、ピクチャーインピクチャー)を要求したり、セーフエリア(ダイナミックアイランド/ノッチ)や最大高さ制約に合わせてレイアウトを適応させたりできます。
主要なメソッド:
requestFullscreen():ホストウィジェット表示をフルスクリーンに切り替えます。requestInline():ホストウィジェット表示をインラインに戻します。requestPip():ウィジェットをピクチャーインピクチャーでフロート表示します。requestClose():ウィジェットを完全に閉じます。
例:
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function StatusBoard() {
const {
requestFullscreen,
requestInline,
requestClose,
displayMode, // リアクティブプロパティ ('fullscreen' | 'inline' | 'pip')
maxHeight, // リアクティブな最大高さ制約(ピクセル単位)
getSafeArea // インセットデータ: { top, right, bottom, left }
} = useWidgetSDK();
const safeArea = getSafeArea() || { top: 0, bottom: 0 };
return (
<div style={{ maxHeight: maxHeight || 400, paddingTop: safeArea.top }}>
<h3>モード: {displayMode}</h3>
<button onClick={requestFullscreen}>フルスクリーン</button>
<button onClick={requestInline}>折りたたむ</button>
<button onClick={requestClose}>ウィジェットを閉じる</button>
</div>
);
}
6. チャットナビゲーションとアクション
ウィジェットは外部ブラウザリンクとフォローアッププロンプトを使用して、ホストのチャットペインとやり取りできます。
主要なメソッド:
openExternal(url):指定されたURLをユーザーの主要な外部ブラウザで安全に開きます。sendFollowUpMessage(prompt):チャットフローにメッセージを挿入し、自動的にLLMエージェントに送信します。
例:
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function MissionControl() {
const { openExternal, sendFollowUpMessage } = useWidgetSDK();
return (
<div>
{/* 外部ドキュメントを開く */}
<button onClick={() => openExternal('https://docs.nitrostack.dev')}>
マニュアルを開く
</button>
{/* ウィジェットから直接LLMエージェントに問い合わせる */}
<button onClick={() => sendFollowUpMessage('先週のミッションの失敗を分析してください')}>
エージェントに分析を依頼
</button>
</div>
);
}
7. メディアとアクセシビリティクエリ
SDKは、スタイリングやアクセシビリティのためにターゲットクライアントの機能を問い合わせるためのヘルパーユーティリティを提供します。
主要なユーティリティ:
prefersReducedMotion():クライアント設定が動きの低減を指定している場合にtrueを返します。アニメーションを無効にします。isPrimarilyTouchDevice():デバイスが粗いポインターを持つ場合(タッチ/モバイルなど)にtrueを返します。ボタンのターゲットサイズを大きくします。isHoverAvailable():ポインターがホバー状態をサポートしている場合にtrueを返します。prefersDarkColorScheme():システムテーマがダークの場合にtrueを返します。
例:
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' }}>
インタラクティブボタン
</button>
</div>
);
}
8. ウィジェットのテスト
- プロジェクトを NitroStudio で開き、ビジュアルプレビューを確認します。
- AIチャットまたはテストペインからツールを呼び出し、返されたJSON構造でウィジェットが即座に更新されることを確認します。