mcpField
W
widget-generator

widget-generator

github.com/f开发174.9k306 次安装今天更新
代码仓库

关于 widget-generator

widget-generator 指导开发者为 prompts.chat 创建并注册 Widget 插件,涵盖标准提示卡片和自定义 React 渲染,并可配置展示位置与注入条件。

主要功能

  1. 01标准提示卡片 Widget
  2. 02自定义 React 渲染
  3. 03位置与重复展示设置
  4. 04基于筛选条件的注入规则
  5. 05赞助方信息与素材
  6. 06类型检查与预览步骤

适用场景

  1. 01在提示词 Feed 中添加赞助卡片
  2. 02渲染自定义交互式 Feed 组件
  3. 03针对特定搜索或分类展示 Widget

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/f/prompts.chat --skill widget-generator

安装后在 Claude Code 中输入/widget-generator确认已加载。

详细介绍

Widget 生成器 Skill

此 Skill 指导如何为 prompts.chat 创建 widget 插件。Widget 会注入提示词信息流,用于展示推广内容、赞助商卡片或自定义交互组件。

概述

Widget 支持两种渲染模式:

  1. 标准提示词 widget - 使用默认的 PromptCard 样式(如 coderabbit.ts)
  2. 自定义渲染 widget - 完全自定义的 React 组件(如 book.tsx)

前提条件

创建 widget 前,请向用户收集以下信息:

参数必填说明
Widget ID✅唯一标识符(kebab-case,例如 my-sponsor)
Widget 名称✅插件的显示名称
渲染模式✅standard 或 custom
赞助商信息❌名称、logo、logoDark、URL(用于赞助商 widget)

步骤 1:收集 Widget 配置

向用户询问以下配置选项:

基本信息
- 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

步骤 2:创建 Widget 文件

标准 Widget(仅 TypeScript)

创建文件: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;
      },
    },
  ],
};
自定义渲染 Widget(使用 React 的 TSX)

创建文件: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 />,
    },
  ],
};

步骤 3:注册 Widget

编辑 src/lib/plugins/widgets/index.ts:

  1. 在顶部添加导入:
import { {widgetId}Widget } from "./{widget-id}";
  1. 添加到 widgetPlugins 数组:
const widgetPlugins: WidgetPlugin[] = [
  coderabbitWidget,
  bookWidget,
  {widgetId}Widget, // Add new widget
];

步骤 4:添加赞助商素材(如适用)

如果 widget 有赞助商:

  1. 添加浅色 logo:public/sponsors/{sponsor}.svg
  2. 添加深色 logo(可选):public/sponsors/{sponsor}-dark.svg

位置示例

在位置 5 显示一次
positioning: {
  position: 5,
  mode: "once",
}
每 30 个项目重复一次,最多 5 次
positioning: {
  position: 3,
  mode: "repeat",
  repeatEvery: 30,
  maxCount: 5,
}
无限重复
positioning: {
  position: 2,
  mode: "repeat",
  repeatEvery: 25,
  // No maxCount = unlimited
}

shouldInject 示例

始终显示
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>
CTA 按钮
<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>

验证

  1. 运行类型检查:

    npx tsc --noEmit
    
  2. 启动开发服务器:

    npm run dev
    
  3. 前往 /discover 或 /feed,确认 widget 出现在配置的位置

类型参考

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[];
}

常见问题

问题解决方案
Widget 未显示检查 shouldInject 逻辑,并确认已在 index.ts 中注册
TypeScript 错误确保从 ./types 导入,并检查赞助商对象的结构
样式问题使用 Tailwind 类,并遵循现有 widget 模式
位置错误请记住位置索引从 0 开始,并检查 repeatEvery 的值

评论

支持 Markdown