사용 시기
이 스킬은 AI 클라이언트 또는 NitroStudio 내에서 사용자 정의 React 콘텐츠를 표시하는 대화형 사용자 인터페이스 위젯을 설계, 구축 또는 수정할 때 사용합니다.
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: 'Get product information by barcode.',
inputSchema: z.object({ barcode: z.string() }),
})
@Widget('product-details') // "product-details" 프론트엔드 구성 요소에 매핑
async fetchProduct(input: { barcode: string }) {
return {
name: 'Super Nitro Energy Drink',
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">Connecting to host...</div>;
}
if (!data) {
return <div className="error">No product data received.</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' })}>
View Alerts
</button>
<p>Current Tab: {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('Diagnostic execution failed.');
} finally {
setIsRunning(false);
}
};
return (
<button onClick={runDiagnostic} disabled={isRunning}>
{isRunning ? 'Running...' : 'Run Diagnostics'}
</button>
);
}
5. 레이아웃 및 디스플레이 제어
위젯은 크기 모드 변경(전체 화면, 인라인, PIP(Picture-in-Picture))을 동적으로 요청하고 안전 영역(다이나믹 아일랜드/노치) 또는 최대 높이 제약 조건에 맞게 레이아웃을 조정할 수 있습니다.
주요 메서드:
requestFullscreen(): 호스트 위젯 디스플레이를 전체 화면으로 전환합니다.requestInline(): 호스트 위젯 디스플레이를 인라인으로 되돌립니다.requestPip(): PIP(Picture-in-Picture) 모드로 위젯을 띄웁니다.requestClose(): 위젯을 완전히 종료합니다.
예시:
import React from 'react';
import { useWidgetSDK } from '@nitrostack/widgets';
export default function StatusBoard() {
const {
requestFullscreen,
requestInline,
requestClose,
displayMode, // 반응형 속성 ('fullscreen' | 'inline' | 'pip')
maxHeight, // 반응형 maxHeight 제약 조건 (픽셀 단위)
getSafeArea // 인셋 데이터: { 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}>Fullscreen</button>
<button onClick={requestInline}>Collapse</button>
<button onClick={requestClose}>Dismiss Widget</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')}>
Open Manual
</button>
{/* 위젯에서 직접 LLM 에이전트에게 질문 */}
<button onClick={() => sendFollowUpMessage('Analyze mission failures from last week')}>
Ask Agent to Analyze
</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' }}>
Interactive Button
</button>
</div>
);
}
8. 위젯 테스트
- 시각적 미리보기를 위해 NitroStudio에서 프로젝트를 엽니다.
- AI 채팅 또는 테스트 창에서 도구를 호출하여 반환된 JSON 구조로 위젯이 즉시 업데이트되는지 확인합니다.