关于 widget-design
widget-design 是面向构建 GPT Apps 和 MCP Apps 对话组件的设计技能,提供平台配置指导,以及针对紧凑、沙盒化视图的布局、交互和状态处理原则。
主要功能
- 01GPT Apps 与 MCP Apps 配置指导
- 02React 或模板字符串处理器选择
- 03紧凑组件布局原则
- 04本地状态与异步状态处理
- 05CSP 和组件元数据检查清单
适用场景
- 01选择组件实现方式
- 02设计单屏对话组件
- 03处理加载与错误状态
- 04检查 GPT Apps 组件元数据
MCP 相关
该技能介绍了 MCP Apps 的 React 组件如何通过最简配置在 ext-apps 客户端中呈现,也涵盖 GPT Apps 配置。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ xmcp-dev/ skills --skill widget-design安装后在 Claude Code 中输入/widget-design确认已加载。
附带文件references/design-principles.md
详细介绍
Widget 设计最佳实践
决策框架
平台选择
根据目标客户端选择:
- GPT Apps:目标是 ChatGPT。需要在
_meta.openai中设置widgetAccessible: true。 - MCP Apps:目标是任何 ext-apps 客户端。配置最少,可自动运行。
处理程序类型选择
| 场景 | 处理程序 | 原因 |
|---|---|---|
| 需要用户交互(按钮、输入框) | React(.tsx) | 使用 hooks 管理状态 |
| 显示外部 Widget 库 | 模板字符串 | 只需加载脚本和样式 |
| 根据工具参数显示动态内容 | React | Props 可自然传递 |
| 不含状态的静态 HTML | 模板字符串 | 更简单、开销更低 |
经验法则:如果不确定,就从 React 开始。之后再转换,比一开始采用简单方案更困难。
Widget 设计原则
1. Widget 不是 Web 应用
Widget 会在对话中内嵌渲染。设计限制包括:
- 无导航:只能提供单屏体验
- 宽度有限:大多数客户端的最大宽度约为 600-700px
- 沙盒隔离:外部资源需要声明 CSP
- 临时性:可能会重新渲染,不要依赖持久化
2. 立即提供价值
无需用户操作即可展示有用内容:
// Bad: Requires click to see anything
export default function Widget() {
const [data, setData] = useState(null);
return <button onClick={fetchData}>Load Data</button>;
}
// Good: Shows data immediately
export default function Widget({ query }) {
const [data, setData] = useState(null);
useEffect(() => { fetchData(query).then(setData); }, [query]);
return data ? <Results data={data} /> : <Loading />;
}
3. 在有限空间中建立视觉层级
宽度有限时,层级更加重要:
<div className="space-y-4">
{/* Label: small, muted, uppercase */}
<div className="text-sm text-zinc-500 uppercase tracking-wider">Temperature</div>
{/* Value: large, prominent */}
<div className="text-5xl font-light">72°F</div>
{/* Supporting: medium, secondary */}
<div className="text-zinc-400">Feels like 68°F</div>
</div>
4. 每个交互元素都需要反馈
用户需要通过视觉反馈确认元素可交互:
// Bad: No visual feedback
<button className="px-4 py-2 bg-white/10">Click</button>
// Good: Hover + transition
<button className="px-4 py-2 bg-white/10 hover:bg-white/20 border border-white/10 hover:border-white/20 transition-all duration-200">
Click
</button>
状态管理指南
保持状态局部且简单
Widget 是隔离的。不要使用 Redux、Zustand 或外部状态。
// Good: Local state with hooks
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
始终处理三种状态
每个异步操作都有三种状态。请全部处理:
export default function Widget() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchData()
.then(setData)
.catch(e => setError(e.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <Loading />;
if (error) return <Error message={error} />;
return <Display data={data} />;
}
在挂载时获取数据,而非点击时
Widget 应立即展示价值:
// Bad: User must click to see data
<button onClick={() => fetchData()}>Load</button>
// Good: Fetch automatically
useEffect(() => { fetchData(); }, []);
常见错误
1. 缺少 widgetAccessible(GPT Apps)
没有此配置,Widget 将无法渲染:
// Broken
_meta: { openai: { toolInvocation: { ... } } }
// Fixed
_meta: { openai: { widgetAccessible: true, toolInvocation: { ... } } }
2. 发起外部请求却未配置 CSP
请求会被静默拦截:
// Broken: fetch fails silently
fetch('{{WEATHER_API_URL}}');
// Fixed: declare in metadata
_meta: {
openai: {
widgetCSP: { connect_domains: ["{{WEATHER_API_BASE_URL}}"] }
}
}
3. 硬编码尺寸
Widget 在不同屏幕尺寸下会显示异常:
// Bad
<div style={{ width: '800px' }}>...</div>
// Good
<div className="w-full max-w-2xl mx-auto">...</div>
4. 没有错误边界
发生崩溃时会显示空白 Widget:
// Always wrap risky operations
try {
const result = JSON.parse(data);
} catch {
return <Error message="Invalid data format" />;
}
平台专属说明
GPT Apps:使用结构化内容与 Widget 通信
将数据从工具传递给 Widget:
return {
structuredContent: { game: "doom", url: "..." },
content: [{ type: "text", text: "Launching DOOM..." }],
};
Widget 通过 useToolOutput() hook 读取数据。
MCP Apps:最少配置
MCP Apps 只需一个 React 组件即可运行:
// This is enough for MCP Apps
export const metadata = { name: "widget", description: "..." };
export default function Widget() { return <div>Hello</div>; }
快速参考
GPT Apps 检查清单
- 在元数据中设置
widgetAccessible: true toolInvocation消息不超过 64 个字符- 为外部域配置 CSP
参考资料
参见 references/design-principles.md,了解:
- 完整的 Widget 示例(计数器、天气、街机游戏)
- 组件模式(卡片、按钮、标签页)
- CSS Modules 示例
- GPT App 提交要求
- 项目结构模板
评论
支持 Markdown