widget-generator by f/prompts.chat
npx skills add https://github.com/f/prompts.chat --skill widget-generator此技能指导为 prompts.chat 创建小部件插件。小部件被注入到提示信息流中,用于显示推广内容、赞助商卡片或自定义交互式组件。
小部件支持两种渲染模式:
PromptCard 样式(如 coderabbit.ts)book.tsx)在创建小部件之前,请从用户处收集以下信息:
| 参数 | 必需 | 描述 |
|---|---|---|
| 小部件 ID | ✅ | 唯一标识符(kebab-case,例如 my-sponsor) |
| 小部件名称 | ✅ |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 插件的显示名称 |
| 渲染模式 | ✅ | standard 或 custom |
| 赞助商信息 | ❌ | 名称、logo、logoDark、URL(用于赞助小部件) |
向用户询问以下配置选项:
- id: string (唯一,kebab-case)
- name: string (显示名称)
- slug: string (URL 友好标识符)
- title: string (卡片标题)
- description: string (卡片描述)
- content: string (提示内容,可以是多行 Markdown)
- type: "TEXT" | "STRUCTURED"
- structuredFormat?: "json" | "yaml" (如果 type 是 STRUCTURED)
- tags?: string[] (例如 ["AI", "Development"])
- category?: string (例如 "Development", "Writing")
- actionUrl?: string (行动号召链接)
- actionLabel?: string (行动号召按钮文本)
- sponsor?: {
name: string
logo: string (浅色模式徽标路径)
logoDark?: string (深色模式徽标路径)
url: string (赞助商网站)
}
- positioning: {
position: number (起始位置,从 0 开始索引,默认值:2)
mode: "once" | "repeat" (默认值:"once")
repeatEvery?: number (用于 repeat 模式,例如 30)
maxCount?: number (最大出现次数,once 模式默认为 1,repeat 模式默认为无限)
}
- shouldInject?: (context) => boolean
上下文包含:
- filters.q: 搜索查询
- filters.category: 分类名称
- filters.categorySlug: 分类 slug
- filters.tag: 标签过滤器
- filters.sort: 排序选项
- itemCount: 信息流中的总项目数
创建文件:src/lib/plugins/widgets/{widget-id}.ts
import type { WidgetPlugin } from "./types";
export const {widgetId}Widget: WidgetPlugin = {
id: "{widget-id}",
name: "{Widget Name}",
prompts: [
{
id: "{prompt-id}",
slug: "{prompt-slug}",
title: "{Title}",
description: "{Description}",
content: `{Multi-line content here}`,
type: "TEXT",
// 可选的赞助商
sponsor: {
name: "{Sponsor Name}",
logo: "/sponsors/{sponsor}.svg",
logoDark: "/sponsors/{sponsor}-dark.svg",
url: "{sponsor-url}",
},
tags: ["{Tag1}", "{Tag2}"],
category: "{Category}",
actionUrl: "{action-url}",
actionLabel: "{Action Label}",
positioning: {
position: 2,
mode: "repeat",
repeatEvery: 50,
maxCount: 3,
},
shouldInject: (context) => {
const { filters } = context;
// 当没有过滤器激活时始终显示
if (!filters?.q && !filters?.category && !filters?.tag) {
return true;
}
// 在此处添加自定义过滤器逻辑
return false;
},
},
],
};
创建文件:src/lib/plugins/widgets/{widget-id}.tsx
import Link from "next/link";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import type { WidgetPlugin } from "./types";
function {WidgetName}Widget() {
return (
<div className="group border rounded-[var(--radius)] overflow-hidden hover:border-foreground/20 transition-colors bg-gradient-to-br from-primary/5 via-background to-primary/10 p-5">
{/* 自定义小部件内容 */}
<div className="flex flex-col items-center gap-4">
{/* 图像/视觉元素 */}
<div className="relative w-full aspect-video">
<Image
src="/path/to/image.jpg"
alt="{Alt text}"
fill
className="object-cover rounded-lg"
/>
</div>
{/* 内容 */}
<div className="w-full text-center">
<h3 className="font-semibold text-base mb-1.5">{Title}</h3>
<p className="text-xs text-muted-foreground mb-4">{Description}</p>
<Button asChild size="sm" className="w-full">
<Link href="{action-url}">{Action Label}</Link>
</Button>
</div>
</div>
</div>
);
}
export const {widgetId}Widget: WidgetPlugin = {
id: "{widget-id}",
name: "{Widget Name}",
prompts: [
{
id: "{prompt-id}",
slug: "{prompt-slug}",
title: "{Title}",
description: "{Description}",
content: "",
type: "TEXT",
tags: ["{Tag1}", "{Tag2}"],
category: "{Category}",
actionUrl: "{action-url}",
actionLabel: "{Action Label}",
positioning: {
position: 10,
mode: "repeat",
repeatEvery: 60,
maxCount: 4,
},
shouldInject: () => true,
render: () => <{WidgetName}Widget />,
},
],
};
编辑 src/lib/plugins/widgets/index.ts:
在顶部添加导入:
import { {widgetId}Widget } from "./{widget-id}";
添加到 widgetPlugins 数组:
const widgetPlugins: WidgetPlugin[] = [ coderabbitWidget, bookWidget, {widgetId}Widget, // 添加新小部件 ];
如果小部件有赞助商:
public/sponsors/{sponsor}.svgpublic/sponsors/{sponsor}-dark.svgpositioning: {
position: 5,
mode: "once",
}
positioning: {
position: 3,
mode: "repeat",
repeatEvery: 30,
maxCount: 5,
}
positioning: {
position: 2,
mode: "repeat",
repeatEvery: 25,
// 没有 maxCount = 无限
}
shouldInject: () => true,
shouldInject: (context) => {
const { filters } = context;
return !filters?.q && !filters?.category && !filters?.tag;
},
shouldInject: (context) => {
const slug = context.filters?.categorySlug?.toLowerCase();
return slug?.includes("development") || slug?.includes("coding");
},
shouldInject: (context) => {
const query = context.filters?.q?.toLowerCase() || "";
return ["ai", "automation", "workflow"].some(kw => query.includes(kw));
},
shouldInject: (context) => {
return (context.itemCount ?? 0) >= 10;
},
<div className="border rounded-[var(--radius)] overflow-hidden bg-gradient-to-br from-primary/5 via-background to-primary/10 p-5">
<div className="flex items-center gap-2 mb-2">
<span className="text-xs font-medium text-primary">Sponsored</span>
</div>
<div className="relative w-full aspect-video">
<Image src="/image.jpg" alt="..." fill className="object-cover" />
</div>
<Button asChild size="sm" className="w-full">
<Link href="https://example.com">
Learn More
<ArrowRight className="ml-2 h-3.5 w-3.5" />
</Link>
</Button>
运行类型检查:
npx tsc --noEmit
启动开发服务器:
npm run dev
导航到 /discover 或 /feed 以验证小部件是否出现在配置的位置
interface WidgetPrompt {
id: string;
slug: string;
title: string;
description: string;
content: string;
type: "TEXT" | "STRUCTURED";
structuredFormat?: "json" | "yaml";
sponsor?: {
name: string;
logo: string;
logoDark?: string;
url: string;
};
tags?: string[];
category?: string;
actionUrl?: string;
actionLabel?: string;
positioning?: {
position?: number; // 默认值:2
mode?: "once" | "repeat"; // 默认值:"once"
repeatEvery?: number; // 用于 repeat 模式
maxCount?: number; // 最大出现次数
};
shouldInject?: (context: WidgetContext) => boolean;
render?: () => ReactNode; // 用于自定义渲染
}
interface WidgetPlugin {
id: string;
name: string;
prompts: WidgetPrompt[];
}
| 问题 | 解决方案 |
|---|---|
| 小部件未显示 | 检查 shouldInject 逻辑,验证在 index.ts 中的注册 |
| TypeScript 错误 | 确保从 ./types 导入,检查赞助商对象结构 |
| 样式问题 | 使用 Tailwind 类,匹配现有小部件模式 |
| 位置错误 | 记住位置是从 0 开始索引的,检查 repeatEvery 值 |
每周安装量
125
仓库
GitHub 星标
154.1K
首次出现
2026年2月20日
安全审计
安装于
codex125
gemini-cli124
github-copilot124
amp124
kimi-cli124
opencode124
This skill guides creation of widget plugins for prompts.chat. Widgets are injected into prompt feeds to display promotional content, sponsor cards, or custom interactive components.
Widgets support two rendering modes:
PromptCard styling (like coderabbit.ts)book.tsx)Before creating a widget, gather from the user:
| Parameter | Required | Description |
|---|---|---|
| Widget ID | ✅ | Unique identifier (kebab-case, e.g., my-sponsor) |
| Widget Name | ✅ | Display name for the plugin |
| Rendering Mode | ✅ | standard or custom |
| Sponsor Info | ❌ | Name, logo, logoDark, URL (for sponsored widgets) |
Ask the user for the following configuration options:
- id: string (unique, kebab-case)
- name: string (display name)
- slug: string (URL-friendly identifier)
- title: string (card title)
- description: string (card description)
- content: string (prompt content, can be multi-line markdown)
- type: "TEXT" | "STRUCTURED"
- structuredFormat?: "json" | "yaml" (if type is STRUCTURED)
- tags?: string[] (e.g., ["AI", "Development"])
- category?: string (e.g., "Development", "Writing")
- actionUrl?: string (CTA link)
- actionLabel?: string (CTA button text)
- sponsor?: {
name: string
logo: string (path to light mode logo)
logoDark?: string (path to dark mode logo)
url: string (sponsor website)
}
- positioning: {
position: number (0-indexed start position, default: 2)
mode: "once" | "repeat" (default: "once")
repeatEvery?: number (for repeat mode, e.g., 30)
maxCount?: number (max occurrences, default: 1 for once, unlimited for repeat)
}
- shouldInject?: (context) => boolean
Context contains:
- filters.q: search query
- filters.category: category name
- filters.categorySlug: category slug
- filters.tag: tag filter
- filters.sort: sort option
- itemCount: total items in feed
Create file: src/lib/plugins/widgets/{widget-id}.ts
import type { WidgetPlugin } from "./types";
export const {widgetId}Widget: WidgetPlugin = {
id: "{widget-id}",
name: "{Widget Name}",
prompts: [
{
id: "{prompt-id}",
slug: "{prompt-slug}",
title: "{Title}",
description: "{Description}",
content: `{Multi-line content here}`,
type: "TEXT",
// Optional sponsor
sponsor: {
name: "{Sponsor Name}",
logo: "/sponsors/{sponsor}.svg",
logoDark: "/sponsors/{sponsor}-dark.svg",
url: "{sponsor-url}",
},
tags: ["{Tag1}", "{Tag2}"],
category: "{Category}",
actionUrl: "{action-url}",
actionLabel: "{Action Label}",
positioning: {
position: 2,
mode: "repeat",
repeatEvery: 50,
maxCount: 3,
},
shouldInject: (context) => {
const { filters } = context;
// Always show when no filters active
if (!filters?.q && !filters?.category && !filters?.tag) {
return true;
}
// Add custom filter logic here
return false;
},
},
],
};
Create file: src/lib/plugins/widgets/{widget-id}.tsx
import Link from "next/link";
import Image from "next/image";
import { Button } from "@/components/ui/button";
import type { WidgetPlugin } from "./types";
function {WidgetName}Widget() {
return (
<div className="group border rounded-[var(--radius)] overflow-hidden hover:border-foreground/20 transition-colors bg-gradient-to-br from-primary/5 via-background to-primary/10 p-5">
{/* Custom widget content */}
<div className="flex flex-col items-center gap-4">
{/* Image/visual element */}
<div className="relative w-full aspect-video">
<Image
src="/path/to/image.jpg"
alt="{Alt text}"
fill
className="object-cover rounded-lg"
/>
</div>
{/* Content */}
<div className="w-full text-center">
<h3 className="font-semibold text-base mb-1.5">{Title}</h3>
<p className="text-xs text-muted-foreground mb-4">{Description}</p>
<Button asChild size="sm" className="w-full">
<Link href="{action-url}">{Action Label}</Link>
</Button>
</div>
</div>
</div>
);
}
export const {widgetId}Widget: WidgetPlugin = {
id: "{widget-id}",
name: "{Widget Name}",
prompts: [
{
id: "{prompt-id}",
slug: "{prompt-slug}",
title: "{Title}",
description: "{Description}",
content: "",
type: "TEXT",
tags: ["{Tag1}", "{Tag2}"],
category: "{Category}",
actionUrl: "{action-url}",
actionLabel: "{Action Label}",
positioning: {
position: 10,
mode: "repeat",
repeatEvery: 60,
maxCount: 4,
},
shouldInject: () => true,
render: () => <{WidgetName}Widget />,
},
],
};
Edit src/lib/plugins/widgets/index.ts:
import { {widgetId}Widget } from "./{widget-id}";
widgetPlugins array:const widgetPlugins: WidgetPlugin[] = [
coderabbitWidget,
bookWidget,
{widgetId}Widget, // Add new widget
];
If the widget has a sponsor:
public/sponsors/{sponsor}.svgpublic/sponsors/{sponsor}-dark.svgpositioning: {
position: 5,
mode: "once",
}
positioning: {
position: 3,
mode: "repeat",
repeatEvery: 30,
maxCount: 5,
}
positioning: {
position: 2,
mode: "repeat",
repeatEvery: 25,
// No maxCount = unlimited
}
shouldInject: () => true,
shouldInject: (context) => {
const { filters } = context;
return !filters?.q && !filters?.category && !filters?.tag;
},
shouldInject: (context) => {
const slug = context.filters?.categorySlug?.toLowerCase();
return slug?.includes("development") || slug?.includes("coding");
},
shouldInject: (context) => {
const query = context.filters?.q?.toLowerCase() || "";
return ["ai", "automation", "workflow"].some(kw => query.includes(kw));
},
shouldInject: (context) => {
return (context.itemCount ?? 0) >= 10;
},
<div className="border rounded-[var(--radius)] overflow-hidden bg-gradient-to-br from-primary/5 via-background to-primary/10 p-5">
<div className="flex items-center gap-2 mb-2">
<span className="text-xs font-medium text-primary">Sponsored</span>
</div>
<div className="relative w-full aspect-video">
<Image src="/image.jpg" alt="..." fill className="object-cover" />
</div>
<Button asChild size="sm" className="w-full">
<Link href="https://example.com">
Learn More
<ArrowRight className="ml-2 h-3.5 w-3.5" />
</Link>
</Button>
Run type check:
npx tsc --noEmit
Start dev server:
npm run dev
Navigate to /discover or /feed to verify widget appears at configured positions
interface WidgetPrompt {
id: string;
slug: string;
title: string;
description: string;
content: string;
type: "TEXT" | "STRUCTURED";
structuredFormat?: "json" | "yaml";
sponsor?: {
name: string;
logo: string;
logoDark?: string;
url: string;
};
tags?: string[];
category?: string;
actionUrl?: string;
actionLabel?: string;
positioning?: {
position?: number; // Default: 2
mode?: "once" | "repeat"; // Default: "once"
repeatEvery?: number; // For repeat mode
maxCount?: number; // Max occurrences
};
shouldInject?: (context: WidgetContext) => boolean;
render?: () => ReactNode; // For custom rendering
}
interface WidgetPlugin {
id: string;
name: string;
prompts: WidgetPrompt[];
}
| Issue | Solution |
|---|---|
| Widget not showing | Check shouldInject logic, verify registration in index.ts |
| TypeScript errors | Ensure imports from ./types, check sponsor object shape |
| Styling issues | Use Tailwind classes, match existing widget patterns |
| Position wrong | Remember positions are 0-indexed, check repeatEvery value |
Weekly Installs
125
Repository
GitHub Stars
154.1K
First Seen
Feb 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex125
gemini-cli124
github-copilot124
amp124
kimi-cli124
opencode124
Tailwind CSS v4 + shadcn/ui 生产级技术栈配置指南与最佳实践
2,600 周安装
Apollo Client 4.x 指南:GraphQL 状态管理库,支持React 19与TypeScript
1,800 周安装
产品经理工具包:RICE优先级排序、客户访谈分析与PRD模板 - 现代产品管理必备
1,900 周安装
AI架构设计师助手 - 15年经验系统设计专家,提供架构决策与设计模式指导
1,900 周安装
Swift Testing Pro - Swift 6.2+ 代码审查与测试规范检查工具
2,000 周安装
小红书笔记分析器 - 全方位内容优化与SEO分析工具,提升曝光率
1,900 周安装
shadcn/ui 组件安装与自定义指南:React 主题化UI组件库完整教程
2,000 周安装