mcpField
W
widget-generator

widget-generator

github.com/f開発174.9k306 回インストール今日更新
リポジトリ

widget-generator について

widget-generator は、prompts.chat 向けのウィジェットプラグイン作成と登録を開発者に案内します。標準カードとカスタム React 表示に対応し、配置や挿入条件も設定できます。

主な機能

  1. 01標準プロンプトカードウィジェット
  2. 02カスタム React レンダリング
  3. 03配置と繰り返し表示の設定
  4. 04フィルターに基づく挿入ルール
  5. 05スポンサー情報と素材
  6. 06型チェックとプレビュー手順

活用シーン

  1. 01プロンプトフィードにスポンサー枠を追加
  2. 02カスタムの対話型フィードコンポーネントを表示
  3. 03特定の検索やカテゴリでウィジェットを表示

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/widget-generatorを入力して読み込まれたか確認します。

詳細

ウィジェットジェネレーターのスキル

このスキルでは、prompts.chat 用のウィジェットプラグインの作成方法を説明します。ウィジェットはプロンプトフィードに挿入され、プロモーションコンテンツ、スポンサーのカード、カスタムのインタラクティブコンポーネントを表示します。

概要

ウィジェットは、次の 2 つのレンダリングモードに対応しています。

  1. 標準プロンプトウィジェット - デフォルトの PromptCard スタイルを使用します(coderabbit.ts のようなもの)
  2. カスタムレンダーウィジェット - React コンポーネントを全面的にカスタマイズします(book.tsx のようなもの)

前提条件

ウィジェットを作成する前に、ユーザーから次の情報を集めてください。

パラメーター必須説明
ウィジェット ID✅一意の識別子(ケバブケース、例:my-sponsor)
ウィジェット名✅プラグインの表示名
レンダリングモード✅standard または custom
スポンサー情報❌名前、ロゴ、logoDark、URL(スポンサー付きウィジェットの場合)

ステップ 1:ウィジェットの設定を集める

次の設定オプションについてユーザーに尋ねてください。

基本情報
- id: string (unique, kebab-case)
- name: string (display name)
- slug: string (URL-friendly identifier)
- title: string (card title)
- description: string (card description)
コンテンツ(standard モードの場合)
- 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:ウィジェットファイルを作成する

標準ウィジェット(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;
      },
    },
  ],
};
カスタムレンダーウィジェット(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:ウィジェットを登録する

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:スポンサーのアセットを追加する(該当する場合)

ウィジェットにスポンサーがいる場合:

  1. ライトロゴを追加します:public/sponsors/{sponsor}.svg
  2. ダークロゴを追加します(任意):public/sponsors/{sponsor}-dark.svg

位置指定の例

位置 5 に 1 回表示する
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 に移動し、設定した位置にウィジェットが表示されることを確認します。

型のリファレンス

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

よくある問題

問題解決策
ウィジェットが表示されないshouldInject のロジックを確認し、index.ts への登録を確認します
TypeScript のエラー./types からインポートされていることを確認し、スポンサーオブジェクトの形状を確認します
スタイルの問題Tailwind クラスを使用し、既存のウィジェットパターンに合わせます
位置が違う位置は 0 始まりであることを忘れずに、repeatEvery の値を確認します

コメント