From addcf4c3002d5f42c458b9ce07c42e31d1f48d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Kadir=20Ak=C4=B1n?= Date: Thu, 19 Feb 2026 20:19:03 +0300 Subject: [PATCH] feat: Add presentation page for prompts.chat --- .windsurf/skills/widget-generator/SKILL.md | 392 +++++++++++++++++++++ src/app/presentation/page.tsx | 235 ++++++++++++ src/components/presentation/SlideDeck.tsx | 158 +++++++++ 3 files changed, 785 insertions(+) create mode 100644 .windsurf/skills/widget-generator/SKILL.md create mode 100644 src/app/presentation/page.tsx create mode 100644 src/components/presentation/SlideDeck.tsx diff --git a/.windsurf/skills/widget-generator/SKILL.md b/.windsurf/skills/widget-generator/SKILL.md new file mode 100644 index 00000000..fc77e0bd --- /dev/null +++ b/.windsurf/skills/widget-generator/SKILL.md @@ -0,0 +1,392 @@ +--- +name: widget-generator +description: Generate customizable widget plugins for the prompts.chat feed system +--- + +# Widget Generator Skill + +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. + +## Overview + +Widgets support two rendering modes: +1. **Standard prompt widget** - Uses default `PromptCard` styling (like `coderabbit.ts`) +2. **Custom render widget** - Full custom React component (like `book.tsx`) + +## Prerequisites + +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) | + +## Step 1: Gather Widget Configuration + +Ask the user for the following configuration options: + +### Basic Info +``` +- id: string (unique, kebab-case) +- name: string (display name) +- slug: string (URL-friendly identifier) +- title: string (card title) +- description: string (card description) +``` + +### Content (for standard mode) +``` +- content: string (prompt content, can be multi-line markdown) +- type: "TEXT" | "STRUCTURED" +- structuredFormat?: "json" | "yaml" (if type is STRUCTURED) +``` + +### Categorization +``` +- tags?: string[] (e.g., ["AI", "Development"]) +- category?: string (e.g., "Development", "Writing") +``` + +### Action Button +``` +- actionUrl?: string (CTA link) +- actionLabel?: string (CTA button text) +``` + +### Sponsor (optional) +``` +- sponsor?: { + name: string + logo: string (path to light mode logo) + logoDark?: string (path to dark mode logo) + url: string (sponsor website) + } +``` + +### Positioning Strategy +``` +- 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) + } +``` + +### Injection Logic +``` +- 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 +``` + +## Step 2: Create Widget File + +### Standard Widget (TypeScript only) + +Create file: `src/lib/plugins/widgets/{widget-id}.ts` + +```typescript +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; + }, + }, + ], +}; +``` + +### Custom Render Widget (TSX with React) + +Create file: `src/lib/plugins/widgets/{widget-id}.tsx` + +```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 ( +
+ {/* Custom widget content */} +
+ {/* Image/visual element */} +
+ {Alt text} +
+ + {/* Content */} +
+

{Title}

+

{Description}

+ +
+
+
+ ); +} + +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 />, + }, + ], +}; +``` + +## Step 3: Register Widget + +Edit `src/lib/plugins/widgets/index.ts`: + +1. Add import at top: +```typescript +import { {widgetId}Widget } from "./{widget-id}"; +``` + +2. Add to `widgetPlugins` array: +```typescript +const widgetPlugins: WidgetPlugin[] = [ + coderabbitWidget, + bookWidget, + {widgetId}Widget, // Add new widget +]; +``` + +## Step 4: Add Sponsor Assets (if applicable) + +If the widget has a sponsor: +1. Add light logo: `public/sponsors/{sponsor}.svg` +2. Add dark logo (optional): `public/sponsors/{sponsor}-dark.svg` + +## Positioning Examples + +### Show once at position 5 +```typescript +positioning: { + position: 5, + mode: "once", +} +``` + +### Repeat every 30 items, max 5 times +```typescript +positioning: { + position: 3, + mode: "repeat", + repeatEvery: 30, + maxCount: 5, +} +``` + +### Unlimited repeating +```typescript +positioning: { + position: 2, + mode: "repeat", + repeatEvery: 25, + // No maxCount = unlimited +} +``` + +## shouldInject Examples + +### Always show +```typescript +shouldInject: () => true, +``` + +### Only when no filters active +```typescript +shouldInject: (context) => { + const { filters } = context; + return !filters?.q && !filters?.category && !filters?.tag; +}, +``` + +### Show for specific categories +```typescript +shouldInject: (context) => { + const slug = context.filters?.categorySlug?.toLowerCase(); + return slug?.includes("development") || slug?.includes("coding"); +}, +``` + +### Show when search matches keywords +```typescript +shouldInject: (context) => { + const query = context.filters?.q?.toLowerCase() || ""; + return ["ai", "automation", "workflow"].some(kw => query.includes(kw)); +}, +``` + +### Show only when enough items +```typescript +shouldInject: (context) => { + return (context.itemCount ?? 0) >= 10; +}, +``` + +## Custom Render Patterns + +### Card with gradient background +```tsx +
+``` + +### Sponsor badge +```tsx +
+ Sponsored +
+``` + +### Responsive image +```tsx +
+ ... +
+``` + +### CTA button +```tsx + +``` + +## Verification + +1. Run type check: + ```bash + npx tsc --noEmit + ``` + +2. Start dev server: + ```bash + npm run dev + ``` + +3. Navigate to `/discover` or `/feed` to verify widget appears at configured positions + +## Type Reference + +```typescript +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[]; +} +``` + +## Common Issues + +| 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 | diff --git a/src/app/presentation/page.tsx b/src/app/presentation/page.tsx new file mode 100644 index 00000000..583b32a3 --- /dev/null +++ b/src/app/presentation/page.tsx @@ -0,0 +1,235 @@ +import { SlideDeck, SlideTitle, SlideContent, SlideHighlight } from "@/components/presentation/SlideDeck"; +import { MoveRight, Star, GitMerge, FileCode2, Sparkles, MessageSquare, Globe, Users, Code, Zap, Lightbulb } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +export const metadata = { + title: "Why prompts.chat? | Presentation", + description: "Discover why prompts are more important than ever in the agentic era.", +}; + +export default function PresentationPage() { + return ( + + {/* 1. Title Slide */} +
+
+ +
+ Why prompts.chat? + Discover why prompts are more important than ever in the agentic era. +

+ Use arrow keys or swipe to navigate +

+
+ + {/* 2. The Genesis */} +
+ + The History of prompts.chat + + From a simple repository to a full-fledged platform. We started as Awesome ChatGPT Prompts, and evolved to support the entire AI community with typed prompts, workflows, and skills. + +
+

+ Awesome ChatGPT Prompts → prompts.chat +

+
+
+ + {/* 3. The Paradigm Shift */} +
+ + The Agentic Era + + Why are prompts still important? Because agents need instructions. As AI becomes more autonomous, the quality of instructions determines the quality of the outcome. + +
+ + {/* 4. The Core Question */} +
+ + Wait, isn't AI getting smarter? + + Yes, but smart AI still needs direction. +

+ A genius without instructions is just a very capable idle machine. +
+
+ + {/* 5. The Agentic Need */} +
+ The Agentic Need + + Agents require precise instructions, constraints, and goal definitions. +
    +
  • What tools can they use?
  • +
  • What are their boundaries?
  • +
  • How should they format the output?
  • +
  • What tone should they adopt?
  • +
+
+
+ + {/* 6. The SLM Revolution */} +
+ + Crucial for SLMs + + Small Language Models (SLMs) are the future of edge computing. They require precise, well-crafted prompts to perform tasks accurately without the massive parameter count of frontier models. + +
+ + {/* 7. SLM Constraints */} +
+ SLM Constraints + + Smaller models lack the vast "common sense" of 1T+ parameter models. +

+ They require highly optimized, battle-tested prompts to punch above their weight class and run efficiently on local devices. +
+
+ + {/* 8. The New Primitive */} +
+ + Prompts are New Code Snippets + + Just as developers copy/paste code snippets from StackOverflow, AI practitioners now share prompt templates. Prompts are the new primitive of programming. + +
+ const codeSnippet = "..." + + const promptTemplate = "..." +
+
+ + {/* 9. Open Source AI */} +
+ + Community Sharing + + Nobody learns in isolation. By sharing prompts, we accelerate the collective understanding of how to communicate with AI. It's the open-source movement for human-AI interaction. + +
+ + {/* 10. Collective Intelligence */} +
+ Collective Intelligence + + We are building the largest open repository of human-AI interaction patterns. +

+ Tested, versioned, and peer-reviewed. +
+
+ + {/* 11. Feature - Typed Prompts */} +
+ + Feature: Typed Prompts + + Natural language is ambiguous. Typed Prompts bring structure. +

+ Define inputs, outputs, and variables systematically so prompts can be executed predictably via APIs. +
+
+ + {/* 12. Feature - Workflows */} +
+
+
+ +
+ +
+
+ Feature: Workflows + + Some tasks are too complex for a single prompt. +

+ Chain multiple prompts together to create Workflows — dividing and conquering complex agentic tasks. +
+
+ + {/* 13. Feature - Agent Skills */} +
+ Feature: Agent Skills + + Equip coding assistants (like Claude, Cursor, Windsurf) with specialized, multi-file capabilities. +

+ Give your AI the context and rules it needs to build within your specific tech stack. +
+
+ + {/* 14. Feature - Change Requests */} +
+ + Feature: Change Requests + + Prompts evolve. We've introduced Pull Requests for Prompts. +

+ Collaborate, suggest improvements, and refine instructions together as a community. +
+
+ + {/* 15. For Developers */} +
+ For Developers + + prompts.chat isn't just for end-users. With features like Agent Skills, Typed Prompts, and API integrations, it's a vital tool for developers building the next generation of AI apps. + +
+ + {/* 16. For Everyone */} +
+ + For Everyone + + Discover, collect, and learn from Promptmasters. +

+ A beautifully designed, accessible platform to find exactly what you need to make AI work for you. +
+
+ + {/* 17. Self-Hosted */} +
+ + Fully Self-Hosted + + Your data, your platform. +

+ prompts.chat is 100% open-source and deployable to any server or cloud provider. Own your organization's prompt library securely. +
+
+ + {/* 18. The Vision */} +
+ The Vision + + To create the standard protocol for human-AI interaction. +

+ Where every great instruction is shared, improved, and accessible to all. +
+
+ + {/* 19. Call to Action */} +
+
+ +
+ Join the Community + + Start exploring, sharing, and building the future of AI interaction today. + +
+ + + + + + +
+
+
+ ); +} diff --git a/src/components/presentation/SlideDeck.tsx b/src/components/presentation/SlideDeck.tsx new file mode 100644 index 00000000..a241b7f4 --- /dev/null +++ b/src/components/presentation/SlideDeck.tsx @@ -0,0 +1,158 @@ +"use client"; + +import { useState, useEffect, useCallback, ReactNode } from "react"; +import { ChevronLeft, ChevronRight, X } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; + +interface SlideDeckProps { + children: ReactNode[]; +} + +export function SlideDeck({ children }: SlideDeckProps) { + const [currentSlide, setCurrentSlide] = useState(0); + const totalSlides = children.length; + + const nextSlide = useCallback(() => { + setCurrentSlide((prev) => Math.min(prev + 1, totalSlides - 1)); + }, [totalSlides]); + + const prevSlide = useCallback(() => { + setCurrentSlide((prev) => Math.max(prev - 1, 0)); + }, []); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "ArrowRight" || e.key === " ") { + e.preventDefault(); + nextSlide(); + } else if (e.key === "ArrowLeft") { + e.preventDefault(); + prevSlide(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [nextSlide, prevSlide]); + + return ( +
+ {/* Top Navigation */} +
+
+ {currentSlide + 1} / {totalSlides} +
+ + + +
+ + {/* Slides Container */} +
+ {children.map((child, index) => { + // Calculate the relative position (-1 is left, 0 is active, 1 is right) + const offset = index - currentSlide; + + let translateX = "translate-x-full"; + if (offset === 0) translateX = "translate-x-0"; + else if (offset < 0) translateX = "-translate-x-full"; + + return ( +
+
+ {child} +
+
+ ); + })} +
+ + {/* Bottom Navigation */} +
+ + + {/* Progress Dots */} +
+ {Array.from({ length: totalSlides }).map((_, i) => ( +
+ + +
+ + {/* Progress Bar */} +
+
+
+
+ ); +} + +// Helper components for slide content +export function SlideTitle({ children, className }: { children: ReactNode; className?: string }) { + return ( +

+ {children} +

+ ); +} + +export function SlideContent({ children, className }: { children: ReactNode; className?: string }) { + return ( +
+ {children} +
+ ); +} + +export function SlideHighlight({ children, className }: { children: ReactNode; className?: string }) { + return ( + + {children} + + ); +}