feat: Add presentation page for prompts.chat
This commit is contained in:
@@ -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 (
|
||||||
|
<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 />,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
<div className="border rounded-[var(--radius)] overflow-hidden bg-gradient-to-br from-primary/5 via-background to-primary/10 p-5">
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sponsor badge
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<span className="text-xs font-medium text-primary">Sponsored</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive image
|
||||||
|
```tsx
|
||||||
|
<div className="relative w-full aspect-video">
|
||||||
|
<Image src="/image.jpg" alt="..." fill className="object-cover" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### CTA button
|
||||||
|
```tsx
|
||||||
|
<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>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 |
|
||||||
@@ -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 (
|
||||||
|
<SlideDeck>
|
||||||
|
{/* 1. Title Slide */}
|
||||||
|
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||||
|
<div className="w-24 h-24 bg-primary/10 rounded-3xl flex items-center justify-center mb-8 rotate-12 transition-transform hover:rotate-0 duration-500">
|
||||||
|
<Sparkles className="w-12 h-12 text-primary" />
|
||||||
|
</div>
|
||||||
|
<SlideTitle className="mb-6">Why prompts.chat?</SlideTitle>
|
||||||
|
<SlideContent className="max-w-3xl mb-12">Discover why prompts are more important than ever in the agentic era.</SlideContent>
|
||||||
|
<p className="text-sm text-muted-foreground animate-pulse mt-8">
|
||||||
|
Use arrow keys <MoveRight className="inline w-4 h-4 mx-1" /> or swipe to navigate
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2. The Genesis */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<Star className="w-12 h-12 text-yellow-500 mb-6" />
|
||||||
|
<SlideTitle>The History of prompts.chat</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
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.
|
||||||
|
</SlideContent>
|
||||||
|
<div className="mt-12 p-6 bg-muted/50 rounded-2xl border border-border max-w-2xl">
|
||||||
|
<p className="text-lg text-foreground font-mono">
|
||||||
|
Awesome ChatGPT Prompts → <SlideHighlight>prompts.chat</SlideHighlight>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3. The Paradigm Shift */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<Zap className="w-12 h-12 text-blue-500 mb-6" />
|
||||||
|
<SlideTitle>The Agentic Era</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Why are prompts still important? Because agents need instructions. As AI becomes more autonomous, the quality of instructions determines the quality of the outcome.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 4. The Core Question */}
|
||||||
|
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||||
|
<Lightbulb className="w-16 h-16 text-yellow-400 mb-8" />
|
||||||
|
<SlideTitle className="text-5xl md:text-6xl">Wait, isn't AI getting smarter?</SlideTitle>
|
||||||
|
<SlideContent className="max-w-4xl mt-8">
|
||||||
|
Yes, but <SlideHighlight>smart AI still needs direction.</SlideHighlight>
|
||||||
|
<br /><br />
|
||||||
|
A genius without instructions is just a very capable idle machine.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 5. The Agentic Need */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<SlideTitle>The Agentic Need</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Agents require precise instructions, constraints, and goal definitions.
|
||||||
|
<ul className="mt-8 space-y-6 list-disc list-inside ml-4 text-foreground/80 text-2xl md:text-3xl">
|
||||||
|
<li>What tools can they use?</li>
|
||||||
|
<li>What are their boundaries?</li>
|
||||||
|
<li>How should they format the output?</li>
|
||||||
|
<li>What tone should they adopt?</li>
|
||||||
|
</ul>
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 6. The SLM Revolution */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<Globe className="w-12 h-12 text-green-500 mb-6" />
|
||||||
|
<SlideTitle>Crucial for SLMs</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
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.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 7. SLM Constraints */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<SlideTitle>SLM Constraints</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Smaller models lack the vast "common sense" of 1T+ parameter models.
|
||||||
|
<br /><br />
|
||||||
|
They require <SlideHighlight>highly optimized, battle-tested prompts</SlideHighlight> to punch above their weight class and run efficiently on local devices.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 8. The New Primitive */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<FileCode2 className="w-12 h-12 text-purple-500 mb-6" />
|
||||||
|
<SlideTitle>Prompts are New Code Snippets</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Just as developers copy/paste code snippets from StackOverflow, AI practitioners now share prompt templates. Prompts are the new primitive of programming.
|
||||||
|
</SlideContent>
|
||||||
|
<div className="mt-12 flex items-center gap-4 text-xl md:text-2xl font-mono text-muted-foreground bg-muted p-6 rounded-xl border max-w-fit">
|
||||||
|
<span>const codeSnippet = "..."</span>
|
||||||
|
<MoveRight className="w-6 h-6 text-primary mx-2" />
|
||||||
|
<span className="text-primary">const promptTemplate = "..."</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 9. Open Source AI */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<Users className="w-12 h-12 text-indigo-500 mb-6" />
|
||||||
|
<SlideTitle>Community Sharing</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
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.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 10. Collective Intelligence */}
|
||||||
|
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||||
|
<SlideTitle className="text-5xl md:text-6xl">Collective Intelligence</SlideTitle>
|
||||||
|
<SlideContent className="max-w-4xl mt-8">
|
||||||
|
We are building the <SlideHighlight>largest open repository</SlideHighlight> of human-AI interaction patterns.
|
||||||
|
<br /><br />
|
||||||
|
Tested, versioned, and peer-reviewed.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 11. Feature - Typed Prompts */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<Code className="w-12 h-12 text-red-500 mb-6" />
|
||||||
|
<SlideTitle>Feature: Typed Prompts</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Natural language is ambiguous. <SlideHighlight>Typed Prompts</SlideHighlight> bring structure.
|
||||||
|
<br /><br />
|
||||||
|
Define inputs, outputs, and variables systematically so prompts can be executed predictably via APIs.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 12. Feature - Workflows */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<div className="flex gap-2 mb-6">
|
||||||
|
<div className="w-8 h-8 rounded bg-primary"></div>
|
||||||
|
<MoveRight className="w-8 h-8 text-muted-foreground" />
|
||||||
|
<div className="w-8 h-8 rounded bg-primary/70"></div>
|
||||||
|
<MoveRight className="w-8 h-8 text-muted-foreground" />
|
||||||
|
<div className="w-8 h-8 rounded bg-primary/40"></div>
|
||||||
|
</div>
|
||||||
|
<SlideTitle>Feature: Workflows</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Some tasks are too complex for a single prompt.
|
||||||
|
<br /><br />
|
||||||
|
Chain multiple prompts together to create <SlideHighlight>Workflows</SlideHighlight> — dividing and conquering complex agentic tasks.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 13. Feature - Agent Skills */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<SlideTitle>Feature: Agent Skills</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Equip coding assistants (like Claude, Cursor, Windsurf) with specialized, multi-file capabilities.
|
||||||
|
<br /><br />
|
||||||
|
Give your AI the context and rules it needs to build within your specific tech stack.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 14. Feature - Change Requests */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<GitMerge className="w-12 h-12 text-orange-500 mb-6" />
|
||||||
|
<SlideTitle>Feature: Change Requests</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Prompts evolve. We've introduced <SlideHighlight>Pull Requests for Prompts.</SlideHighlight>
|
||||||
|
<br /><br />
|
||||||
|
Collaborate, suggest improvements, and refine instructions together as a community.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 15. For Developers */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<SlideTitle>For Developers</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
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.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 16. For Everyone */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<MessageSquare className="w-12 h-12 text-pink-500 mb-6" />
|
||||||
|
<SlideTitle>For Everyone</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Discover, collect, and learn from <SlideHighlight>Promptmasters</SlideHighlight>.
|
||||||
|
<br /><br />
|
||||||
|
A beautifully designed, accessible platform to find exactly what you need to make AI work for you.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 17. Self-Hosted */}
|
||||||
|
<div className="flex flex-col justify-center h-full">
|
||||||
|
<Globe className="w-12 h-12 text-teal-500 mb-6" />
|
||||||
|
<SlideTitle>Fully Self-Hosted</SlideTitle>
|
||||||
|
<SlideContent>
|
||||||
|
Your data, your platform.
|
||||||
|
<br /><br />
|
||||||
|
prompts.chat is <SlideHighlight>100% open-source</SlideHighlight> and deployable to any server or cloud provider. Own your organization's prompt library securely.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 18. The Vision */}
|
||||||
|
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||||
|
<SlideTitle className="text-5xl md:text-6xl">The Vision</SlideTitle>
|
||||||
|
<SlideContent className="max-w-4xl mt-8">
|
||||||
|
To create the <SlideHighlight>standard protocol</SlideHighlight> for human-AI interaction.
|
||||||
|
<br /><br />
|
||||||
|
Where every great instruction is shared, improved, and accessible to all.
|
||||||
|
</SlideContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 19. Call to Action */}
|
||||||
|
<div className="flex flex-col items-center justify-center text-center h-full">
|
||||||
|
<div className="w-24 h-24 bg-primary/10 rounded-full flex items-center justify-center mb-8">
|
||||||
|
<Users className="w-12 h-12 text-primary" />
|
||||||
|
</div>
|
||||||
|
<SlideTitle>Join the Community</SlideTitle>
|
||||||
|
<SlideContent className="max-w-2xl mb-12">
|
||||||
|
Start exploring, sharing, and building the future of AI interaction today.
|
||||||
|
</SlideContent>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
|
<Link href="/discover">
|
||||||
|
<Button size="lg" className="text-lg px-8 py-6 rounded-xl">Explore Prompts</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href="https://github.com/fka/awesome-chatgpt-prompts" target="_blank">
|
||||||
|
<Button size="lg" variant="outline" className="text-lg px-8 py-6 rounded-xl">Star on GitHub</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SlideDeck>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className="relative h-screen w-screen overflow-hidden bg-background text-foreground flex flex-col">
|
||||||
|
{/* Top Navigation */}
|
||||||
|
<div className="absolute top-4 right-4 z-50 flex items-center gap-4">
|
||||||
|
<div className="text-sm font-medium text-muted-foreground bg-background/50 backdrop-blur-sm px-3 py-1 rounded-full">
|
||||||
|
{currentSlide + 1} / {totalSlides}
|
||||||
|
</div>
|
||||||
|
<Link href="/">
|
||||||
|
<Button variant="ghost" size="icon" className="rounded-full bg-background/50 backdrop-blur-sm hover:bg-background/80">
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Slides Container */}
|
||||||
|
<div className="flex-1 relative">
|
||||||
|
{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 (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={cn(
|
||||||
|
"absolute inset-0 transition-all duration-500 ease-in-out flex items-center justify-center p-8 md:p-16 lg:p-24",
|
||||||
|
translateX,
|
||||||
|
offset === 0 ? "opacity-100 scale-100" : "opacity-0 scale-95 pointer-events-none"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="w-full max-w-5xl mx-auto">
|
||||||
|
{child}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Navigation */}
|
||||||
|
<div className="absolute bottom-8 left-0 right-0 z-50 flex justify-center items-center gap-8">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={prevSlide}
|
||||||
|
disabled={currentSlide === 0}
|
||||||
|
className="rounded-full w-12 h-12 bg-background/50 backdrop-blur-sm hover:bg-background/80"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-6 h-6" />
|
||||||
|
<span className="sr-only">Previous</span>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Progress Dots */}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{Array.from({ length: totalSlides }).map((_, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => setCurrentSlide(i)}
|
||||||
|
className={cn(
|
||||||
|
"w-2.5 h-2.5 rounded-full transition-all duration-300",
|
||||||
|
i === currentSlide
|
||||||
|
? "bg-primary w-8"
|
||||||
|
: "bg-muted-foreground/30 hover:bg-muted-foreground/50"
|
||||||
|
)}
|
||||||
|
aria-label={`Go to slide ${i + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={nextSlide}
|
||||||
|
disabled={currentSlide === totalSlides - 1}
|
||||||
|
className="rounded-full w-12 h-12 bg-background/50 backdrop-blur-sm hover:bg-background/80"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-6 h-6" />
|
||||||
|
<span className="sr-only">Next</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Bar */}
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 h-1.5 bg-muted">
|
||||||
|
<div
|
||||||
|
className="h-full bg-primary transition-all duration-500 ease-out"
|
||||||
|
style={{ width: `${((currentSlide + 1) / totalSlides) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper components for slide content
|
||||||
|
export function SlideTitle({ children, className }: { children: ReactNode; className?: string }) {
|
||||||
|
return (
|
||||||
|
<h1 className={cn("text-4xl md:text-6xl lg:text-7xl font-bold tracking-tight mb-8 md:mb-12 text-balance leading-tight bg-clip-text text-transparent bg-gradient-to-br from-foreground to-foreground/70", className)}>
|
||||||
|
{children}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SlideContent({ children, className }: { children: ReactNode; className?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={cn("text-xl md:text-3xl lg:text-4xl leading-relaxed text-muted-foreground font-medium", className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SlideHighlight({ children, className }: { children: ReactNode; className?: string }) {
|
||||||
|
return (
|
||||||
|
<span className={cn("text-primary font-semibold", className)}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user