使用时机
在设计、构建或修改用于在 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: '通过条形码获取产品信息。',
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">正在连接到主机...</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>
{/* 直接从 widget 询问 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 结构即时更新。