Loading...
Loading...
Compare original and translation side by side
_meta.openaiwidgetAccessible: true_meta.openaiwidgetAccessible: true| Scenario | Handler | Reason |
|---|---|---|
| User interaction needed (buttons, inputs) | React ( | State management with hooks |
| Display external widget library | Template literal | Just load scripts/styles |
| Dynamic content from tool params | React | Props flow naturally |
| Static HTML with no state | Template literal | Simpler, less overhead |
| 场景 | 处理方式 | 原因 |
|---|---|---|
| 需要用户交互(按钮、输入框) | React( | 使用Hooks进行状态管理 |
| 展示外部组件库 | 模板字面量 | 仅需加载脚本/样式 |
| 从工具参数获取动态内容 | React | Props可自然传递 |
| 无状态的静态HTML | 模板字面量 | 更简单、开销更低 |
// 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 />;
}// 不佳:需要点击才能查看内容
export default function Widget() {
const [data, setData] = useState(null);
return <button onClick={fetchData}>加载数据</button>;
}
// 推荐:立即展示数据
export default function Widget({ query }) {
const [data, setData] = useState(null);
useEffect(() => { fetchData(query).then(setData); }, [query]);
return data ? <Results data={data} /> : <Loading />;
}<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><div className="space-y-4">
{/* 标签:小字号、浅色调、大写 */}
<div className="text-sm text-zinc-500 uppercase tracking-wider">温度</div>
{/* 数值:大字号、突出显示 */}
<div className="text-5xl font-light">72°F</div>
{/* 辅助信息:中字号、次要色调 */}
<div className="text-zinc-400">体感温度 68°F</div>
</div>// 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>// 不佳:无视觉反馈
<button className="px-4 py-2 bg-white/10">点击</button>
// 推荐:悬停效果 + 过渡动画
<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">
点击
</button>// Good: Local state with hooks
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);// 推荐:使用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} />;
}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} />;
}// Bad: User must click to see data
<button onClick={() => fetchData()}>Load</button>
// Good: Fetch automatically
useEffect(() => { fetchData(); }, []);// 不佳:用户必须点击才能查看数据
<button onClick={() => fetchData()}>加载</button>
// 推荐:自动获取数据
useEffect(() => { fetchData(); }, []);// Broken
_meta: { openai: { toolInvocation: { ... } } }
// Fixed
_meta: { openai: { widgetAccessible: true, toolInvocation: { ... } } }// 错误配置
_meta: { openai: { toolInvocation: { ... } } }
// 修复后
_meta: { openai: { widgetAccessible: true, toolInvocation: { ... } } }// Broken: fetch fails silently
fetch('https://api.weather.com/data');
// Fixed: declare in metadata
_meta: {
openai: {
widgetCSP: { connect_domains: ["https://api.weather.com"] }
}
}// 错误写法:请求会静默失败
fetch('https://api.weather.com/data');
// 修复后:在元数据中声明
_meta: {
openai: {
widgetCSP: { connect_domains: ["https://api.weather.com"] }
}
}// Bad
<div style={{ width: '800px' }}>...</div>
// Good
<div className="w-full max-w-2xl mx-auto">...</div>// 不佳
<div style={{ width: '800px' }}>...</div>
// 推荐
<div className="w-full max-w-2xl mx-auto">...</div>// Always wrap risky operations
try {
const result = JSON.parse(data);
} catch {
return <Error message="Invalid data format" />;
}// 始终对风险操作进行包裹
try {
const result = JSON.parse(data);
} catch {
return <Error message="数据格式无效" />;
}return {
structuredContent: { game: "doom", url: "..." },
content: [{ type: "text", text: "Launching DOOM..." }],
};useToolOutput()return {
structuredContent: { game: "doom", url: "..." },
content: [{ type: "text", text: "正在启动DOOM..." }],
};useToolOutput()// This is enough for MCP Apps
export const metadata = { name: "widget", description: "..." };
export default function Widget() { return <div>Hello</div>; }// 此配置足以运行MCP Apps
export const metadata = { name: "widget", description: "..." };
export default function Widget() { return <div>你好</div>; }widgetAccessible: truetoolInvocationwidgetAccessible: truetoolInvocation