Communitygithub.com

joshi-chinmay-016/AgentProof

@Widget 및 @nitrostack/widgets SDK를 사용하여 대화형 프론트엔드 위젯에 도구를 연결하기 위한 모범 사례(상태 동기화, 도구 호출, 디스플레이 모드, 미디어 쿼리 및 채팅 작업 포함)

AgentProof란 무엇인가요?

이 스킬은 AI 클라이언트 또는 NitroStudio 내에서 사용자 정의 React 콘텐츠를 표시하는 대화형 사용자 인터페이스 위젯을 설계, 구축 또는 수정하는 방법을 설명합니다. `@Widget` 데코레이터를 사용하여 백엔드 도구를 프론트엔드 React 구성 요소에 연결하는 방법을 다루고, `useWidgetSDK` 훅을 통해 도구 출력 데이터를 수신하는 방법을 보여줍니다. 또한 `useWidgetState`를 사용한 상태 관리 및 동기화, `callTool`을 사용한 프론트엔드에서의 추가 도구 호출, 전체 화면, 인라인, PIP(Picture-in-Picture)와 같은 다양한 위젯 디스플레이 모드를 위한 레이아웃 제어 기능을 포함합니다. 또한 외부 URL 열기 및 LLM 에이전트에 후속 메시지 보내기를 위한 채팅 탐색 방법과 접근성 및 미디어 쿼리 유틸리티를 제공하여 반응형 및 접근 가능한 위젯을 만드는 데 도움을 줍니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/joshi-chinmay-016/AgentProof/tree/HEAD/agentproof/.agents/skills/ui-widgets

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

사용 시기

이 스킬은 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 구조로 위젯이 즉시 업데이트되는지 확인합니다.

관련 스킬