mcpField
W
widget-design

widget-design

github.com/xmcp-dev设计与 UI044 次安装36 周前更新
代码仓库

关于 widget-design

widget-design 是面向构建 GPT Apps 和 MCP Apps 对话组件的设计技能,提供平台配置指导,以及针对紧凑、沙盒化视图的布局、交互和状态处理原则。

主要功能

  1. 01GPT Apps 与 MCP Apps 配置指导
  2. 02React 或模板字符串处理器选择
  3. 03紧凑组件布局原则
  4. 04本地状态与异步状态处理
  5. 05CSP 和组件元数据检查清单

适用场景

  1. 01选择组件实现方式
  2. 02设计单屏对话组件
  3. 03处理加载与错误状态
  4. 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 库模板字符串只需加载脚本和样式
根据工具参数显示动态内容ReactProps 可自然传递
不含状态的静态 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