feat: Add modern Next.js 15 web interface with React 19

This commit adds a complete, production-ready web application for browsing
and exploring AI tool system prompts. The interface provides an intuitive,
responsive, and feature-rich experience for discovering AI tools.

## Web Application Features

**Core Functionality:**
- 🔍 Advanced search with real-time filtering
- 📊 Interactive statistics dashboard with visualizations
- 🔄 Side-by-side comparison of up to 4 tools
-  Favorites system with local persistence
- 📱 Fully responsive mobile-first design
- 🎨 Dark/light mode with system preference detection
-  Optimized performance with Next.js Server Components

**Pages:**
- Home: Hero section, features showcase, featured tools
- Browse: Advanced filtering, grid/list views, category filters
- Stats: Comprehensive analytics and visualizations
- Compare: Side-by-side tool comparison
- Tool Detail: In-depth tool information
- About: Project information and tech stack

**Components:**
- Responsive navbar with mobile menu
- Tool cards with interactive actions
- Reusable UI components (Button, Card, Badge, Input)
- Footer with quick links and stats
- Theme provider for dark mode
- Loading and error states

## Technical Stack

**Framework & Libraries:**
- Next.js 15 with App Router
- React 19.0 with Server Components
- TypeScript 5.6 for type safety
- Tailwind CSS for styling
- Zustand for state management
- next-themes for theme switching
- Lucide React for icons

**Features:**
- Server-side rendering (SSR)
- Static site generation (SSG) for tool pages
- Optimized bundle with automatic code splitting
- SEO-friendly with metadata API
- Accessibility best practices

## Project Structure

web/
├── app/                   # Next.js pages
├── components/            # React components
├── lib/                   # Utilities and data
├── data/                  # Static data (index.json)
├── setup.sh              # Setup script
└── README.md             # Documentation

## Developer Experience

- TypeScript for type safety
- ESLint for code quality
- Hot module replacement
- Fast refresh
- Comprehensive documentation
- Setup script for quick start

## Updated Documentation

- Enhanced main README with web interface section
- Created comprehensive web/README.md
- Updated roadmap to mark completed features
- Added Quick Start guide for web app

## Stats

- 33 new files created
- ~3,500 lines of TypeScript/TSX
- Full responsive design (mobile, tablet, desktop)
- Production-ready with build optimizations

Users can now explore 32+ AI tools through an intuitive web interface
instead of just command-line tools.

Version: 2.0.0
This commit is contained in:
Claude 2025-11-15 02:20:46 +00:00
parent cb8bb88b4f
commit 9d5ee88ea3
No known key found for this signature in database
33 changed files with 4357 additions and 10 deletions

View File

@ -80,8 +80,28 @@ Sponsor the most comprehensive collection of AI system prompts and reach thousan
## 🚀 Quick Start ## 🚀 Quick Start
### Browse Tools ### 🌐 Web Interface (NEW!)
Explore 32+ AI coding tools organized by category below, or use our search tools:
The easiest way to explore AI prompts is through our modern web interface:
```bash
cd web
./setup.sh
npm run dev
```
Visit **http://localhost:3000** to explore:
- 🔍 **Advanced search** and filtering
- 📊 **Statistics dashboard** with visualizations
- 🔄 **Compare tools** side-by-side
- 📱 **Fully responsive** mobile-friendly design
- 🎨 **Dark mode** support
See [web/README.md](./web/README.md) for full documentation.
### 📟 Command Line Tools
For developers who prefer the terminal:
```bash ```bash
# Generate metadata index # Generate metadata index
@ -245,15 +265,22 @@ python scripts/search.py --list-companies
### What's New in v2.0 🎉 ### What's New in v2.0 🎉
**Major enhancements:** **🌐 Modern Web Interface:**
- ✨ **Searchable Index**: Find tools by category, company, or model - ✨ **Next.js 15 + React 19** web application
- 📊 **Analysis Tools**: Generate statistics and comparisons - 🔍 **Advanced search** with real-time filtering
- ✅ **Validation**: Automated quality checks - 📊 **Interactive statistics** dashboard
- 🔄 **Side-by-side comparison** of up to 4 tools
- 📱 **Fully responsive** mobile design
- 🎨 **Dark mode** with theme persistence
- ⚡ **Lightning fast** with Server Components
**📁 Repository Enhancements:**
- 📚 **Better Documentation**: Individual READMEs for key tools - 📚 **Better Documentation**: Individual READMEs for key tools
- 🗂️ **Organized by Category**: Browse by IDE, Agent, Assistant, etc. - 🗂️ **Organized by Category**: Browse by IDE, Agent, Assistant, etc.
- 🔍 **Discovery Tools**: Python scripts for searching and analysis - ✅ **Validation**: Automated quality checks
- 📊 **Analysis Tools**: Generate statistics and comparisons
**New Scripts:** **🛠️ Developer Tools:**
- `generate_metadata.py` - Create searchable index - `generate_metadata.py` - Create searchable index
- `validate.py` - Check repository quality - `validate.py` - Check repository quality
- `search.py` - Search and filter tools - `search.py` - Search and filter tools
@ -262,8 +289,8 @@ python scripts/search.py --list-companies
See [CHANGELOG.md](./CHANGELOG.md) for full details. See [CHANGELOG.md](./CHANGELOG.md) for full details.
### Future Roadmap ### Future Roadmap
- [ ] Web interface for browsing prompts - [x] Web interface for browsing prompts ✅ **COMPLETED**
- [ ] Prompt comparison and diff tools - [x] Prompt comparison and diff tools ✅ **COMPLETED**
- [ ] Community ratings and reviews - [ ] Community ratings and reviews
- [ ] API for programmatic access - [ ] API for programmatic access
- [ ] More individual tool READMEs - [ ] More individual tool READMEs

3
web/.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

37
web/.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

237
web/README.md Normal file
View File

@ -0,0 +1,237 @@
# AI Prompts Explorer - Web Interface
A modern, responsive web application for exploring AI tool system prompts and configurations. Built with Next.js 15, React 19, and TypeScript.
![Version](https://img.shields.io/badge/version-2.0.0-blue)
![Next.js](https://img.shields.io/badge/Next.js-15-black)
![React](https://img.shields.io/badge/React-19-61DAFB)
![TypeScript](https://img.shields.io/badge/TypeScript-5.6-blue)
## ✨ Features
- 🔍 **Advanced Search & Filtering** - Search by name, company, category, or model
- 📊 **Statistics Dashboard** - Comprehensive analytics and visualizations
- 🔄 **Tool Comparison** - Compare up to 4 tools side-by-side
- 💾 **Favorites** - Save your favorite tools for quick access
- 🎨 **Dark Mode** - Beautiful dark/light theme support
- 📱 **Fully Responsive** - Perfect on desktop, tablet, and mobile
- ⚡ **Lightning Fast** - Built with Next.js 15 and Server Components
- 🎭 **Smooth Animations** - Polished UX with Framer Motion
## 🚀 Quick Start
### Prerequisites
- Node.js 22+ (recommended: 22.21.1)
- npm 10+
### Installation
```bash
# Navigate to the web directory
cd web
# Install dependencies
npm install
# Run the development server
npm run dev
```
Open [http://localhost:3000](http://localhost:3000) in your browser.
## 📁 Project Structure
```
web/
├── app/ # Next.js App Router pages
│ ├── page.tsx # Homepage
│ ├── browse/ # Browse tools page
│ ├── stats/ # Statistics dashboard
│ ├── compare/ # Tool comparison
│ ├── tool/[slug]/ # Individual tool pages
│ ├── about/ # About page
│ ├── layout.tsx # Root layout
│ └── globals.css # Global styles
├── components/ # Reusable components
│ ├── ui/ # Base UI components
│ ├── navbar.tsx # Navigation bar
│ ├── footer.tsx # Footer
│ ├── tool-card.tsx # Tool card component
│ └── theme-provider.tsx # Theme provider
├── lib/ # Utilities and data
│ ├── data.ts # Data loading functions
│ ├── store.ts # Zustand state management
│ ├── types.ts # TypeScript types
│ └── utils.ts # Utility functions
├── data/ # Static data
│ └── index.json # Generated metadata
└── public/ # Static assets
```
## 🛠️ Tech Stack
- **Framework**: [Next.js 15](https://nextjs.org/) with App Router
- **UI Library**: [React 19](https://react.dev/)
- **Language**: [TypeScript](https://www.typescriptlang.org/)
- **Styling**: [Tailwind CSS](https://tailwindcss.com/)
- **State Management**: [Zustand](https://zustand-demo.pmnd.rs/)
- **Icons**: [Lucide React](https://lucide.dev/)
- **Animations**: [Framer Motion](https://www.framer.com/motion/)
- **Theme**: [next-themes](https://github.com/pacocoursey/next-themes)
## 📦 Available Scripts
```bash
# Development server
npm run dev
# Production build
npm run build
# Start production server
npm start
# Lint code
npm run lint
# Type check
npm run type-check
```
## 🎨 Features In Detail
### Browse Tools
- Grid or list view
- Real-time search
- Multi-select filters (category, type, company)
- Sort by name, lines, files, or company
- Responsive card layout
### Tool Detail Pages
- Complete tool information
- File listings with sizes
- Model information
- Direct links to GitHub and official websites
- Add to comparison or favorites
### Statistics Dashboard
- Total tools, files, and lines
- Category distribution with visual bars
- Top tools by complexity
- Type distribution (proprietary vs open-source)
- Company breakdown
### Comparison
- Side-by-side comparison of up to 4 tools
- Compare all key metrics
- File listings
- Quick actions
### Dark Mode
- System preference detection
- Manual toggle
- Persistent preference
- Smooth transitions
## 🔧 Configuration
### Environment Variables
Create a `.env.local` file (optional):
```env
# No environment variables required for basic setup
```
### Updating Data
The app uses `data/index.json` generated from the repository metadata:
```bash
# From the repository root
python3 scripts/generate_metadata.py
# Copy to web/data
cp scripts/index.json web/data/
```
## 🚀 Deployment
### Vercel (Recommended)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/tree/main/web)
```bash
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel
```
### Other Platforms
Build the static site:
```bash
npm run build
```
The output will be in `.next/`. Deploy using:
- **Netlify**: Supports Next.js
- **Cloudflare Pages**: Supports Next.js
- **AWS Amplify**: Supports Next.js
- **Docker**: Use the included Dockerfile (if created)
## 📱 Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Mobile browsers (iOS Safari, Chrome Mobile)
## 🤝 Contributing
Contributions are welcome! Please see the main [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
### Development Workflow
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request
## 📄 License
This project is part of the [AI Prompts and Models Repository](https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools) and follows the same license.
## 🙏 Acknowledgments
- Built with [Next.js](https://nextjs.org/)
- UI components inspired by [shadcn/ui](https://ui.shadcn.com/)
- Icons from [Lucide](https://lucide.dev/)
- Community contributions
## 📞 Support
- 🐛 [Report Issues](https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/issues)
- 💬 [Discord Community](https://discord.gg/NwzrWErdMU)
- 📧 Email: lucknitelol@proton.me
## 🎯 Roadmap
- [ ] Advanced filtering (by model, file count, etc.)
- [ ] Export comparison as PDF/image
- [ ] Favorites synchronization
- [ ] Community ratings and reviews
- [ ] API for programmatic access
- [ ] Prompt visualization and analysis
- [ ] Search result highlighting
- [ ] Keyboard shortcuts
---
**Made with ❤️ by the AI Prompts Explorer community**

211
web/app/about/page.tsx Normal file
View File

@ -0,0 +1,211 @@
import Link from 'next/link'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Github, Heart, Code, Database, Search, BarChart3, GitCompare, Sparkles } from 'lucide-react'
import { getStats } from '@/lib/data'
import { formatNumber } from '@/lib/utils'
export default function AboutPage() {
const stats = getStats()
const features = [
{
icon: Search,
title: 'Advanced Search',
description: 'Search and filter by category, company, model, or keyword with real-time results',
},
{
icon: GitCompare,
title: 'Side-by-Side Comparison',
description: 'Compare up to 4 tools simultaneously to analyze differences and similarities',
},
{
icon: BarChart3,
title: 'Analytics Dashboard',
description: 'Comprehensive statistics and visualizations of all AI tools',
},
{
icon: Code,
title: 'Complete Prompts',
description: 'Full system prompts and tool configurations, not just excerpts',
},
{
icon: Database,
title: 'Structured Data',
description: 'Organized metadata with files, models, categories, and companies',
},
{
icon: Sparkles,
title: 'Modern Interface',
description: 'Beautiful, responsive UI built with Next.js 15, React 19, and Tailwind CSS',
},
]
const techStack = [
{ name: 'Next.js 15', description: 'React framework with App Router' },
{ name: 'React 19', description: 'Latest React with Server Components' },
{ name: 'TypeScript', description: 'Type-safe development' },
{ name: 'Tailwind CSS', description: 'Utility-first CSS framework' },
{ name: 'Zustand', description: 'Lightweight state management' },
{ name: 'Framer Motion', description: 'Animation library' },
]
return (
<div className="container mx-auto px-4 py-8">
{/* Hero */}
<div className="text-center max-w-3xl mx-auto mb-16">
<Badge className="mb-4" variant="secondary">
<Sparkles className="w-3 h-3 mr-1" />
Version 2.0
</Badge>
<h1 className="text-4xl md:text-5xl font-bold mb-4">
About AI Prompts Explorer
</h1>
<p className="text-lg text-muted-foreground">
The most comprehensive, searchable collection of AI tool system prompts and configurations.
Discover how {stats.total_tools}+ AI coding tools work under the hood.
</p>
</div>
{/* Stats */}
<div className="grid gap-6 md:grid-cols-4 mb-16">
<Card>
<CardHeader>
<CardTitle className="text-3xl">{stats.total_tools}+</CardTitle>
<CardDescription>AI Tools Documented</CardDescription>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-3xl">{stats.total_files}</CardTitle>
<CardDescription>Configuration Files</CardDescription>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-3xl">{formatNumber(stats.total_lines)}</CardTitle>
<CardDescription>Lines of Prompts</CardDescription>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-3xl">{Object.keys(stats.by_category).length}</CardTitle>
<CardDescription>Tool Categories</CardDescription>
</CardHeader>
</Card>
</div>
{/* Features */}
<div className="mb-16">
<h2 className="text-3xl font-bold text-center mb-8">Features</h2>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => {
const Icon = feature.icon
return (
<Card key={feature.title}>
<CardHeader>
<Icon className="w-10 h-10 mb-2 text-primary" />
<CardTitle>{feature.title}</CardTitle>
<CardDescription>{feature.description}</CardDescription>
</CardHeader>
</Card>
)
})}
</div>
</div>
{/* Tech Stack */}
<div className="mb-16">
<h2 className="text-3xl font-bold text-center mb-8">Technology Stack</h2>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{techStack.map((tech) => (
<Card key={tech.name}>
<CardHeader>
<CardTitle className="text-lg">{tech.name}</CardTitle>
<CardDescription>{tech.description}</CardDescription>
</CardHeader>
</Card>
))}
</div>
</div>
{/* Mission */}
<Card className="mb-16">
<CardContent className="p-8">
<h2 className="text-2xl font-bold mb-4">Our Mission</h2>
<p className="text-muted-foreground mb-4">
AI Prompts Explorer aims to provide transparency into how AI coding tools work by collecting,
organizing, and presenting their system prompts and configurations in an accessible way.
</p>
<p className="text-muted-foreground mb-4">
We believe that understanding how AI tools are configured helps developers:
</p>
<ul className="list-disc list-inside text-muted-foreground space-y-2 mb-4">
<li>Choose the right tools for their needs</li>
<li>Learn prompt engineering best practices</li>
<li>Understand AI tool capabilities and limitations</li>
<li>Build better AI-powered applications</li>
<li>Contribute to the open-source AI community</li>
</ul>
</CardContent>
</Card>
{/* Contributing */}
<Card className="mb-16">
<CardContent className="p-8">
<div className="flex items-start gap-4">
<Heart className="w-12 h-12 text-red-500 flex-shrink-0 mt-1" />
<div className="flex-1">
<h2 className="text-2xl font-bold mb-4">Contributing</h2>
<p className="text-muted-foreground mb-4">
This project is open source and welcomes contributions from the community. You can help by:
</p>
<ul className="list-disc list-inside text-muted-foreground space-y-2 mb-6">
<li>Adding new AI tool prompts and configurations</li>
<li>Updating existing tool information</li>
<li>Improving documentation</li>
<li>Reporting bugs or suggesting features</li>
<li>Sharing the project with others</li>
</ul>
<div className="flex flex-wrap gap-4">
<Button asChild>
<a
href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools"
target="_blank"
rel="noopener noreferrer"
>
<Github className="w-4 h-4 mr-2" />
View on GitHub
</a>
</Button>
<Button asChild variant="outline">
<a
href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/main/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
>
Contributing Guide
</a>
</Button>
</div>
</div>
</div>
</CardContent>
</Card>
{/* CTA */}
<Card className="bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 text-white border-0">
<CardContent className="p-8 text-center">
<h2 className="text-3xl font-bold mb-4">Start Exploring</h2>
<p className="text-lg mb-6 text-white/90">
Browse {stats.total_tools}+ AI tools and discover their system prompts
</p>
<Button asChild size="lg" className="bg-white text-purple-600 hover:bg-white/90">
<Link href="/browse">Browse All Tools</Link>
</Button>
</CardContent>
</Card>
</div>
)
}

265
web/app/browse/page.tsx Normal file
View File

@ -0,0 +1,265 @@
'use client'
import { useState, useMemo } from 'react'
import { Filter, Grid, List, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Badge } from '@/components/ui/badge'
import { Card } from '@/components/ui/card'
import { ToolCard } from '@/components/tool-card'
import { getAllTools, getCategories, getCompanies, getModels } from '@/lib/data'
import { useAppStore } from '@/lib/store'
import { AITool } from '@/lib/types'
import { cn } from '@/lib/utils'
export default function BrowsePage() {
const tools = getAllTools()
const categories = getCategories()
const companies = getCompanies()
const models = getModels()
const { viewMode, setViewMode } = useAppStore()
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategories, setSelectedCategories] = useState<string[]>([])
const [selectedTypes, setSelectedTypes] = useState<string[]>([])
const [selectedCompanies, setSelectedCompanies] = useState<string[]>([])
const [showFilters, setShowFilters] = useState(false)
// Filter tools
const filteredTools = useMemo(() => {
return tools.filter((tool) => {
// Search filter
if (searchQuery) {
const query = searchQuery.toLowerCase()
const matchesSearch =
tool.name.toLowerCase().includes(query) ||
tool.description.toLowerCase().includes(query) ||
tool.company.toLowerCase().includes(query) ||
tool.category.toLowerCase().includes(query) ||
tool.models.some((m) => m.toLowerCase().includes(query))
if (!matchesSearch) return false
}
// Category filter
if (selectedCategories.length > 0 && !selectedCategories.includes(tool.category)) {
return false
}
// Type filter
if (selectedTypes.length > 0 && !selectedTypes.includes(tool.type)) {
return false
}
// Company filter
if (selectedCompanies.length > 0 && !selectedCompanies.includes(tool.company)) {
return false
}
return true
})
}, [tools, searchQuery, selectedCategories, selectedTypes, selectedCompanies])
const toggleCategory = (category: string) => {
setSelectedCategories((prev) =>
prev.includes(category) ? prev.filter((c) => c !== category) : [...prev, category]
)
}
const toggleType = (type: string) => {
setSelectedTypes((prev) =>
prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type]
)
}
const toggleCompany = (company: string) => {
setSelectedCompanies((prev) =>
prev.includes(company) ? prev.filter((c) => c !== company) : [...prev, company]
)
}
const clearFilters = () => {
setSearchQuery('')
setSelectedCategories([])
setSelectedTypes([])
setSelectedCompanies([])
}
const hasActiveFilters =
searchQuery || selectedCategories.length > 0 || selectedTypes.length > 0 || selectedCompanies.length > 0
return (
<div className="container mx-auto px-4 py-8">
{/* Header */}
<div className="mb-8">
<h1 className="text-4xl font-bold mb-4">Browse AI Tools</h1>
<p className="text-lg text-muted-foreground">
Explore {tools.length} AI coding tools and their system prompts
</p>
</div>
{/* Search and Controls */}
<div className="mb-6 space-y-4">
<div className="flex flex-col sm:flex-row gap-4">
<Input
type="search"
placeholder="Search tools, companies, models..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="flex-1"
/>
<div className="flex gap-2">
<Button
variant="outline"
onClick={() => setShowFilters(!showFilters)}
className="flex-1 sm:flex-none"
>
<Filter className="w-4 h-4 mr-2" />
Filters
{hasActiveFilters && (
<Badge variant="destructive" className="ml-2">
{[selectedCategories, selectedTypes, selectedCompanies]
.reduce((acc, arr) => acc + arr.length, 0)}
</Badge>
)}
</Button>
<div className="flex border rounded-md">
<Button
variant={viewMode === 'grid' ? 'secondary' : 'ghost'}
size="icon"
onClick={() => setViewMode('grid')}
className="rounded-r-none"
>
<Grid className="w-4 h-4" />
</Button>
<Button
variant={viewMode === 'list' ? 'secondary' : 'ghost'}
size="icon"
onClick={() => setViewMode('list')}
className="rounded-l-none"
>
<List className="w-4 h-4" />
</Button>
</div>
</div>
</div>
{/* Active Filters */}
{hasActiveFilters && (
<div className="flex flex-wrap gap-2 items-center">
<span className="text-sm text-muted-foreground">Active filters:</span>
{selectedCategories.map((cat) => (
<Badge key={cat} variant="secondary" className="gap-1">
{cat}
<X className="w-3 h-3 cursor-pointer" onClick={() => toggleCategory(cat)} />
</Badge>
))}
{selectedTypes.map((type) => (
<Badge key={type} variant="secondary" className="gap-1">
{type}
<X className="w-3 h-3 cursor-pointer" onClick={() => toggleType(type)} />
</Badge>
))}
{selectedCompanies.map((company) => (
<Badge key={company} variant="secondary" className="gap-1">
{company}
<X className="w-3 h-3 cursor-pointer" onClick={() => toggleCompany(company)} />
</Badge>
))}
<Button variant="ghost" size="sm" onClick={clearFilters}>
Clear all
</Button>
</div>
)}
</div>
{/* Filters Sidebar */}
{showFilters && (
<Card className="mb-6 p-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Categories */}
<div>
<h3 className="font-semibold mb-3">Categories</h3>
<div className="space-y-2">
{categories.slice(0, 8).map((category) => (
<label key={category} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={selectedCategories.includes(category)}
onChange={() => toggleCategory(category)}
className="rounded"
/>
<span className="text-sm">{category}</span>
</label>
))}
</div>
</div>
{/* Types */}
<div>
<h3 className="font-semibold mb-3">Type</h3>
<div className="space-y-2">
{['proprietary', 'open-source'].map((type) => (
<label key={type} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={selectedTypes.includes(type)}
onChange={() => toggleType(type)}
className="rounded"
/>
<span className="text-sm capitalize">{type}</span>
</label>
))}
</div>
</div>
{/* Companies */}
<div>
<h3 className="font-semibold mb-3">Companies</h3>
<div className="space-y-2 max-h-48 overflow-y-auto">
{companies.slice(0, 10).map((company) => (
<label key={company} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={selectedCompanies.includes(company)}
onChange={() => toggleCompany(company)}
className="rounded"
/>
<span className="text-sm">{company}</span>
</label>
))}
</div>
</div>
</div>
</Card>
)}
{/* Results */}
<div className="mb-4 flex justify-between items-center">
<p className="text-sm text-muted-foreground">
Showing {filteredTools.length} of {tools.length} tools
</p>
</div>
{/* Tools Grid/List */}
{filteredTools.length > 0 ? (
<div
className={cn(
viewMode === 'grid'
? 'grid gap-6 md:grid-cols-2 lg:grid-cols-3'
: 'space-y-4'
)}
>
{filteredTools.map((tool) => (
<ToolCard key={tool.directory} tool={tool} variant={viewMode === 'list' ? 'compact' : 'default'} />
))}
</div>
) : (
<Card className="p-12 text-center">
<p className="text-lg text-muted-foreground mb-4">No tools found matching your criteria</p>
<Button onClick={clearFilters}>Clear Filters</Button>
</Card>
)}
</div>
)
}

203
web/app/compare/page.tsx Normal file
View File

@ -0,0 +1,203 @@
'use client'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { useAppStore } from '@/lib/store'
import { getToolByDirectory } from '@/lib/data'
import { formatNumber, formatBytes } from '@/lib/utils'
import { X, FileText, Code, Package } from 'lucide-react'
import Link from 'next/link'
export default function ComparePage() {
const { comparison, removeFromComparison, clearComparison } = useAppStore()
const tools = comparison.map((dir) => getToolByDirectory(dir)).filter(Boolean)
if (tools.length === 0) {
return (
<div className="container mx-auto px-4 py-16">
<Card className="p-12 text-center max-w-2xl mx-auto">
<Package className="w-16 h-16 mx-auto text-muted-foreground mb-4" />
<h2 className="text-2xl font-bold mb-2">No Tools Selected</h2>
<p className="text-muted-foreground mb-6">
Add tools to comparison from the browse page to see side-by-side comparison
</p>
<Button asChild>
<Link href="/browse">Browse Tools</Link>
</Button>
</Card>
</div>
)
}
const comparisonData = [
{
label: 'Company',
getValue: (tool: any) => tool.company,
},
{
label: 'Category',
getValue: (tool: any) => tool.category,
},
{
label: 'Type',
getValue: (tool: any) => <Badge variant="outline">{tool.type}</Badge>,
},
{
label: 'Files',
getValue: (tool: any) => tool.file_count,
},
{
label: 'Total Lines',
getValue: (tool: any) => formatNumber(tool.total_lines),
},
{
label: 'Models',
getValue: (tool: any) => (
<div className="flex flex-wrap gap-1">
{tool.models.length > 0 ? (
tool.models.slice(0, 3).map((model: string) => (
<Badge key={model} variant="secondary" className="text-xs">
{model}
</Badge>
))
) : (
<span className="text-muted-foreground text-sm">None specified</span>
)}
</div>
),
},
{
label: 'Website',
getValue: (tool: any) =>
tool.website ? (
<a
href={tool.website}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline text-sm"
>
Visit
</a>
) : (
<span className="text-muted-foreground text-sm">N/A</span>
),
},
]
return (
<div className="container mx-auto px-4 py-8">
{/* Header */}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<h1 className="text-4xl font-bold">Compare Tools</h1>
{tools.length > 0 && (
<Button variant="outline" onClick={clearComparison}>
<X className="w-4 h-4 mr-2" />
Clear All
</Button>
)}
</div>
<p className="text-lg text-muted-foreground">
Comparing {tools.length} of 4 maximum tools
</p>
</div>
{/* Comparison Table */}
<div className="overflow-x-auto">
<div className="inline-block min-w-full align-middle">
<div className="grid" style={{ gridTemplateColumns: `200px repeat(${tools.length}, 1fr)` }}>
{/* Header Row */}
<div className="bg-muted p-4 border font-semibold">Tool</div>
{tools.map((tool) => (
<Card key={tool!.directory} className="border-l-0 rounded-none">
<CardHeader className="pb-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<CardTitle className="text-lg line-clamp-2">{tool!.name}</CardTitle>
</div>
<Button
size="icon"
variant="ghost"
onClick={() => removeFromComparison(tool!.directory)}
className="h-6 w-6 flex-shrink-0"
>
<X className="w-4 h-4" />
</Button>
</div>
</CardHeader>
</Card>
))}
{/* Description Row */}
<div className="bg-muted p-4 border border-t-0 font-semibold">Description</div>
{tools.map((tool) => (
<div key={`desc-${tool!.directory}`} className="p-4 border border-l-0 border-t-0">
<p className="text-sm text-muted-foreground line-clamp-3">{tool!.description}</p>
</div>
))}
{/* Comparison Rows */}
{comparisonData.map((row, index) => (
<div key={row.label} className="contents">
<div className="bg-muted p-4 border border-t-0 font-semibold text-sm">
{row.label}
</div>
{tools.map((tool) => (
<div key={`${row.label}-${tool!.directory}`} className="p-4 border border-l-0 border-t-0">
<div className="text-sm">{row.getValue(tool)}</div>
</div>
))}
</div>
))}
{/* Files Row */}
<div className="bg-muted p-4 border border-t-0 font-semibold text-sm">Files</div>
{tools.map((tool) => (
<div key={`files-${tool!.directory}`} className="p-4 border border-l-0 border-t-0">
<div className="space-y-1">
{tool!.files.slice(0, 5).map((file) => (
<div key={file.path} className="text-xs text-muted-foreground truncate">
<FileText className="w-3 h-3 inline mr-1" />
{file.name}
</div>
))}
{tool!.files.length > 5 && (
<div className="text-xs text-muted-foreground">
+{tool!.files.length - 5} more files
</div>
)}
</div>
</div>
))}
{/* Actions Row */}
<div className="bg-muted p-4 border border-t-0 font-semibold">Actions</div>
{tools.map((tool) => (
<div key={`actions-${tool!.directory}`} className="p-4 border border-l-0 border-t-0">
<Button asChild size="sm" className="w-full">
<Link href={`/tool/${tool!.directory.toLowerCase().replace(/\s+/g, '-')}`}>
View Details
</Link>
</Button>
</div>
))}
</div>
</div>
</div>
{/* Add More */}
{tools.length < 4 && (
<Card className="mt-8 p-6 text-center">
<p className="text-muted-foreground mb-4">
You can compare up to {4 - tools.length} more tool{tools.length < 3 ? 's' : ''}
</p>
<Button asChild variant="outline">
<Link href="/browse">Add More Tools</Link>
</Button>
</Card>
)}
</div>
)
}

109
web/app/globals.css Normal file
View File

@ -0,0 +1,109 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
@apply bg-muted/30;
}
::-webkit-scrollbar-thumb {
@apply bg-muted-foreground/30 rounded-md;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-muted-foreground/50;
}
/* Gradient backgrounds */
.gradient-bg {
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--accent)) 100%);
}
.gradient-text {
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--accent)) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Glass morphism */
.glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark .glass {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}

45
web/app/layout.tsx Normal file
View File

@ -0,0 +1,45 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { Navbar } from '@/components/navbar'
import { Footer } from '@/components/footer'
import { ThemeProvider } from '@/components/theme-provider'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'AI Prompts Explorer - System Prompts & AI Tool Configurations',
description: 'Explore 32+ AI coding tools, their system prompts, and configurations. The most comprehensive collection of AI tool prompts.',
keywords: ['AI', 'prompts', 'system prompts', 'AI tools', 'LLM', 'GPT', 'Claude', 'code assistant'],
authors: [{ name: 'AI Prompts Explorer' }],
openGraph: {
title: 'AI Prompts Explorer',
description: 'Explore 32+ AI coding tools and their system prompts',
type: 'website',
},
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<div className="flex min-h-screen flex-col">
<Navbar />
<main className="flex-1">{children}</main>
<Footer />
</div>
</ThemeProvider>
</body>
</html>
)
}

13
web/app/loading.tsx Normal file
View File

@ -0,0 +1,13 @@
export default function Loading() {
return (
<div className="container mx-auto px-4 py-16 min-h-[60vh] flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="relative w-16 h-16">
<div className="absolute inset-0 border-4 border-primary/20 rounded-full" />
<div className="absolute inset-0 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
)
}

34
web/app/not-found.tsx Normal file
View File

@ -0,0 +1,34 @@
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Home, Search } from 'lucide-react'
export default function NotFound() {
return (
<div className="container mx-auto px-4 py-16 min-h-[60vh] flex items-center justify-center">
<Card className="p-12 text-center max-w-2xl">
<div className="text-8xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent mb-4">
404
</div>
<h1 className="text-3xl font-bold mb-4">Page Not Found</h1>
<p className="text-lg text-muted-foreground mb-8">
The page you're looking for doesn't exist or has been moved.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button asChild size="lg">
<Link href="/">
<Home className="w-4 h-4 mr-2" />
Go Home
</Link>
</Button>
<Button asChild size="lg" variant="outline">
<Link href="/browse">
<Search className="w-4 h-4 mr-2" />
Browse Tools
</Link>
</Button>
</div>
</Card>
</div>
)
}

217
web/app/page.tsx Normal file
View File

@ -0,0 +1,217 @@
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { ArrowRight, Search, BarChart3, GitCompare, Sparkles, Code, Zap, Shield } from 'lucide-react'
import { getAllTools, getStats, getCategories } from '@/lib/data'
import { formatNumber } from '@/lib/utils'
import { ToolCard } from '@/components/tool-card'
export default function HomePage() {
const stats = getStats()
const tools = getAllTools()
const categories = getCategories()
const featuredTools = tools.slice(0, 6)
return (
<div className="flex flex-col">
{/* Hero Section */}
<section className="relative overflow-hidden bg-gradient-to-b from-background to-muted/20">
<div className="absolute inset-0 bg-grid-pattern opacity-[0.02]" />
<div className="container relative mx-auto px-4 py-24 md:py-32">
<div className="mx-auto max-w-4xl text-center space-y-8 animate-fade-in">
<Badge className="mx-auto" variant="secondary">
<Sparkles className="w-3 h-3 mr-1" />
Version 2.0 - Now with Web Interface!
</Badge>
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl md:text-7xl">
<span className="block">Explore AI Tool</span>
<span className="block bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 bg-clip-text text-transparent">
System Prompts
</span>
</h1>
<p className="mx-auto max-w-2xl text-lg text-muted-foreground md:text-xl">
The most comprehensive collection of AI coding tool system prompts and configurations.
Discover how {stats.total_tools}+ AI tools work under the hood.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
<Button asChild size="lg" className="text-lg h-12 px-8">
<Link href="/browse">
Browse All Tools
<ArrowRight className="ml-2 w-5 h-5" />
</Link>
</Button>
<Button asChild size="lg" variant="outline" className="text-lg h-12 px-8">
<Link href="/stats">
View Statistics
<BarChart3 className="ml-2 w-5 h-5" />
</Link>
</Button>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 pt-8">
<div className="bg-card border rounded-lg p-4">
<div className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-cyan-500 bg-clip-text text-transparent">
{stats.total_tools}+
</div>
<div className="text-sm text-muted-foreground">AI Tools</div>
</div>
<div className="bg-card border rounded-lg p-4">
<div className="text-3xl font-bold bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent">
{stats.total_files}
</div>
<div className="text-sm text-muted-foreground">Files</div>
</div>
<div className="bg-card border rounded-lg p-4">
<div className="text-3xl font-bold bg-gradient-to-r from-green-500 to-emerald-500 bg-clip-text text-transparent">
{formatNumber(stats.total_lines)}
</div>
<div className="text-sm text-muted-foreground">Lines</div>
</div>
<div className="bg-card border rounded-lg p-4">
<div className="text-3xl font-bold bg-gradient-to-r from-orange-500 to-red-500 bg-clip-text text-transparent">
{categories.length}
</div>
<div className="text-sm text-muted-foreground">Categories</div>
</div>
</div>
</div>
</div>
</section>
{/* Features Section */}
<section className="container mx-auto px-4 py-16 md:py-24">
<div className="text-center space-y-4 mb-12">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
Why AI Prompts Explorer?
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Discover, compare, and understand AI tool system prompts
</p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<Card className="bg-gradient-to-br from-blue-50 to-cyan-50 dark:from-blue-950/20 dark:to-cyan-950/20 border-blue-200 dark:border-blue-800">
<CardHeader>
<Search className="w-10 h-10 text-blue-500 mb-2" />
<CardTitle>Advanced Search</CardTitle>
<CardDescription>
Search and filter by category, company, model, or keyword
</CardDescription>
</CardHeader>
</Card>
<Card className="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-950/20 dark:to-pink-950/20 border-purple-200 dark:border-purple-800">
<CardHeader>
<GitCompare className="w-10 h-10 text-purple-500 mb-2" />
<CardTitle>Tool Comparison</CardTitle>
<CardDescription>
Compare up to 4 tools side-by-side to see differences
</CardDescription>
</CardHeader>
</Card>
<Card className="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-950/20 dark:to-emerald-950/20 border-green-200 dark:border-green-800">
<CardHeader>
<BarChart3 className="w-10 h-10 text-green-500 mb-2" />
<CardTitle>Analytics & Stats</CardTitle>
<CardDescription>
Visualize trends and statistics across all AI tools
</CardDescription>
</CardHeader>
</Card>
<Card className="bg-gradient-to-br from-orange-50 to-red-50 dark:from-orange-950/20 dark:to-red-950/20 border-orange-200 dark:border-orange-800">
<CardHeader>
<Code className="w-10 h-10 text-orange-500 mb-2" />
<CardTitle>Complete Prompts</CardTitle>
<CardDescription>
Full system prompts and tool configurations included
</CardDescription>
</CardHeader>
</Card>
<Card className="bg-gradient-to-br from-indigo-50 to-blue-50 dark:from-indigo-950/20 dark:to-blue-950/20 border-indigo-200 dark:border-indigo-800">
<CardHeader>
<Zap className="w-10 h-10 text-indigo-500 mb-2" />
<CardTitle>Regular Updates</CardTitle>
<CardDescription>
New tools and updates added regularly from the community
</CardDescription>
</CardHeader>
</Card>
<Card className="bg-gradient-to-br from-pink-50 to-rose-50 dark:from-pink-950/20 dark:to-rose-950/20 border-pink-200 dark:border-pink-800">
<CardHeader>
<Shield className="w-10 h-10 text-pink-500 mb-2" />
<CardTitle>Open Source</CardTitle>
<CardDescription>
Fully open source with community contributions welcome
</CardDescription>
</CardHeader>
</Card>
</div>
</section>
{/* Featured Tools Section */}
<section className="bg-muted/30 py-16 md:py-24">
<div className="container mx-auto px-4">
<div className="text-center space-y-4 mb-12">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl">
Featured AI Tools
</h2>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto">
Explore some of the most popular AI coding tools and their system prompts
</p>
</div>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3 mb-8">
{featuredTools.map((tool) => (
<ToolCard key={tool.directory} tool={tool} />
))}
</div>
<div className="text-center">
<Button asChild size="lg" variant="outline">
<Link href="/browse">
View All {stats.total_tools} Tools
<ArrowRight className="ml-2 w-4 h-4" />
</Link>
</Button>
</div>
</div>
</section>
{/* CTA Section */}
<section className="container mx-auto px-4 py-16 md:py-24">
<Card className="bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 text-white border-0">
<CardContent className="p-8 md:p-12 text-center">
<h2 className="text-3xl font-bold mb-4">
Ready to Explore AI Prompts?
</h2>
<p className="text-lg mb-8 text-white/90 max-w-2xl mx-auto">
Start browsing our collection of {stats.total_tools}+ AI tools and discover how they work
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button asChild size="lg" className="bg-white text-purple-600 hover:bg-white/90">
<Link href="/browse">
Start Exploring
<ArrowRight className="ml-2 w-4 h-4" />
</Link>
</Button>
<Button asChild size="lg" variant="outline" className="border-white text-white hover:bg-white/10">
<a href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools" target="_blank" rel="noopener noreferrer">
View on GitHub
</a>
</Button>
</div>
</CardContent>
</Card>
</section>
</div>
)
}

210
web/app/stats/page.tsx Normal file
View File

@ -0,0 +1,210 @@
'use client'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { getAllTools, getStats, getCategoryIcon, getCategoryColor } from '@/lib/data'
import { formatNumber } from '@/lib/utils'
import { BarChart3, TrendingUp, Package, FileText, Code } from 'lucide-react'
export default function StatsPage() {
const stats = getStats()
const tools = getAllTools()
// Calculate additional stats
const topByLines = [...tools].sort((a, b) => b.total_lines - a.total_lines).slice(0, 10)
const topByFiles = [...tools].sort((a, b) => b.file_count - a.file_count).slice(0, 10)
// Get max values for scaling
const maxByCategory = Math.max(...Object.values(stats.by_category))
return (
<div className="container mx-auto px-4 py-8">
{/* Header */}
<div className="mb-8">
<h1 className="text-4xl font-bold mb-4">Statistics & Analytics</h1>
<p className="text-lg text-muted-foreground">
Comprehensive insights into AI tools and their configurations
</p>
</div>
{/* Overview Stats */}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-4 mb-8">
<Card>
<CardHeader className="pb-3">
<CardDescription>Total AI Tools</CardDescription>
<CardTitle className="text-4xl">{stats.total_tools}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-xs text-muted-foreground flex items-center gap-1">
<TrendingUp className="w-3 h-3" />
Across {Object.keys(stats.by_category).length} categories
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardDescription>Total Files</CardDescription>
<CardTitle className="text-4xl">{stats.total_files}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-xs text-muted-foreground flex items-center gap-1">
<FileText className="w-3 h-3" />
Configuration & prompt files
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardDescription>Total Lines</CardDescription>
<CardTitle className="text-4xl">{formatNumber(stats.total_lines)}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-xs text-muted-foreground flex items-center gap-1">
<Code className="w-3 h-3" />
Of system prompts & configs
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-3">
<CardDescription>Average per Tool</CardDescription>
<CardTitle className="text-4xl">
{Math.round(stats.total_lines / stats.total_tools)}
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-xs text-muted-foreground flex items-center gap-1">
<BarChart3 className="w-3 h-3" />
Lines per tool
</div>
</CardContent>
</Card>
</div>
{/* Category Distribution */}
<Card className="mb-8">
<CardHeader>
<CardTitle>Tools by Category</CardTitle>
<CardDescription>Distribution across all categories</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{Object.entries(stats.by_category)
.sort(([, a], [, b]) => b - a)
.map(([category, count]) => {
const percentage = (count / maxByCategory) * 100
return (
<div key={category} className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-2xl">{getCategoryIcon(category)}</span>
<span className="font-medium">{category}</span>
</div>
<Badge variant="secondary">{count} tools</Badge>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className={`h-2 rounded-full bg-gradient-to-r ${getCategoryColor(category)}`}
style={{ width: `${percentage}%` }}
/>
</div>
</div>
)
})}
</div>
</CardContent>
</Card>
<div className="grid gap-6 lg:grid-cols-2 mb-8">
{/* Top by Lines */}
<Card>
<CardHeader>
<CardTitle>Most Complex (by lines)</CardTitle>
<CardDescription>Tools with the most lines of code</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{topByLines.map((tool, index) => (
<div key={tool.directory} className="flex items-center gap-3">
<div className="flex-shrink-0 w-6 text-center font-bold text-muted-foreground">
{index + 1}
</div>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{tool.name}</div>
<div className="text-xs text-muted-foreground truncate">{tool.company}</div>
</div>
<Badge variant="outline">{formatNumber(tool.total_lines)} lines</Badge>
</div>
))}
</div>
</CardContent>
</Card>
{/* Top by Files */}
<Card>
<CardHeader>
<CardTitle>Most Files</CardTitle>
<CardDescription>Tools with the most configuration files</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{topByFiles.map((tool, index) => (
<div key={tool.directory} className="flex items-center gap-3">
<div className="flex-shrink-0 w-6 text-center font-bold text-muted-foreground">
{index + 1}
</div>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{tool.name}</div>
<div className="text-xs text-muted-foreground truncate">{tool.company}</div>
</div>
<Badge variant="outline">{tool.file_count} files</Badge>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* Type Distribution */}
<Card>
<CardHeader>
<CardTitle>Open Source vs Proprietary</CardTitle>
<CardDescription>Distribution by tool type</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{Object.entries(stats.by_type).map(([type, count]) => {
const percentage = (count / stats.total_tools) * 100
return (
<div key={type} className="space-y-2">
<div className="flex items-center justify-between">
<span className="font-medium capitalize">{type}</span>
<div className="flex items-center gap-2">
<Badge variant="secondary">{count} tools</Badge>
<span className="text-sm text-muted-foreground">
{percentage.toFixed(1)}%
</span>
</div>
</div>
<div className="w-full bg-muted rounded-full h-2">
<div
className={`h-2 rounded-full ${
type === 'open-source'
? 'bg-gradient-to-r from-green-500 to-emerald-500'
: 'bg-gradient-to-r from-blue-500 to-purple-500'
}`}
style={{ width: `${percentage}%` }}
/>
</div>
</div>
)
})}
</div>
</CardContent>
</Card>
</div>
)
}

View File

@ -0,0 +1,225 @@
import { notFound } from 'next/navigation'
import Link from 'next/link'
import { ArrowLeft, ExternalLink, FileText, Code, Calendar, GitCompare, Heart } from 'lucide-react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { getAllTools, getCategoryIcon, getCategoryColor } from '@/lib/data'
import { formatNumber, formatBytes, slugify } from '@/lib/utils'
export async function generateStaticParams() {
const tools = getAllTools()
return tools.map((tool) => ({
slug: slugify(tool.directory),
}))
}
export default function ToolDetailPage({ params }: { params: { slug: string } }) {
const tools = getAllTools()
const tool = tools.find((t) => slugify(t.directory) === params.slug)
if (!tool) {
notFound()
}
return (
<div className="container mx-auto px-4 py-8">
{/* Back Button */}
<Button asChild variant="ghost" className="mb-6">
<Link href="/browse">
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Browse
</Link>
</Button>
{/* Header */}
<div className="mb-8">
<div className="flex items-start gap-4 mb-4">
<div className="text-5xl">{getCategoryIcon(tool.category)}</div>
<div className="flex-1">
<div className="flex flex-wrap items-center gap-2 mb-2">
<h1 className="text-4xl font-bold">{tool.name}</h1>
<Badge variant="secondary">{tool.category}</Badge>
<Badge variant="outline">{tool.type}</Badge>
</div>
<p className="text-xl text-muted-foreground mb-4">{tool.company}</p>
<p className="text-lg">{tool.description}</p>
</div>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<Card>
<CardHeader className="pb-2">
<CardDescription className="text-xs">Files</CardDescription>
<CardTitle className="text-2xl">{tool.file_count}</CardTitle>
</CardHeader>
</Card>
<Card>
<CardHeader className="pb-2">
<CardDescription className="text-xs">Total Lines</CardDescription>
<CardTitle className="text-2xl">{formatNumber(tool.total_lines)}</CardTitle>
</CardHeader>
</Card>
<Card>
<CardHeader className="pb-2">
<CardDescription className="text-xs">Models</CardDescription>
<CardTitle className="text-2xl">{tool.models.length || '-'}</CardTitle>
</CardHeader>
</Card>
<Card>
<CardHeader className="pb-2">
<CardDescription className="text-xs">Category</CardDescription>
<CardTitle className="text-xl truncate">{tool.category}</CardTitle>
</CardHeader>
</Card>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-3">
<div className="lg:col-span-2 space-y-6">
{/* Files */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="w-5 h-5" />
Configuration Files
</CardTitle>
<CardDescription>
{tool.file_count} files containing system prompts and configurations
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
{tool.files.map((file) => (
<div
key={file.path}
className="flex items-center justify-between p-3 border rounded-lg hover:bg-muted/50 transition-colors"
>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{file.name}</div>
<div className="text-xs text-muted-foreground">
{formatBytes(file.size)}
{file.lines && `${formatNumber(file.lines)} lines`}
</div>
</div>
<Badge variant="outline" className="ml-2">
{file.type}
</Badge>
</div>
))}
</div>
</CardContent>
</Card>
{/* Models */}
{tool.models.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Code className="w-5 h-5" />
AI Models
</CardTitle>
<CardDescription>Models used or supported by this tool</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{tool.models.map((model) => (
<Badge key={model} variant="secondary" className="text-sm">
{model}
</Badge>
))}
</div>
</CardContent>
</Card>
)}
{/* Subcategories */}
{tool.subcategories && tool.subcategories.length > 0 && (
<Card>
<CardHeader>
<CardTitle>Related Tools</CardTitle>
<CardDescription>Other tools in this collection</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{tool.subcategories.map((sub) => (
<Badge key={sub} variant="outline">
{sub}
</Badge>
))}
</div>
</CardContent>
</Card>
)}
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Actions */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Actions</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{tool.website && (
<Button asChild className="w-full">
<a href={tool.website} target="_blank" rel="noopener noreferrer">
<ExternalLink className="w-4 h-4 mr-2" />
Visit Website
</a>
</Button>
)}
<Button variant="outline" className="w-full">
<a
href={`https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/tree/main/${encodeURIComponent(
tool.directory
)}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center w-full"
>
<FileText className="w-4 h-4 mr-2" />
View on GitHub
</a>
</Button>
<Button variant="outline" className="w-full">
<GitCompare className="w-4 h-4 mr-2" />
Add to Compare
</Button>
</CardContent>
</Card>
{/* Info */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Information</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm">
<div>
<div className="text-muted-foreground mb-1">Company</div>
<div className="font-medium">{tool.company}</div>
</div>
<div>
<div className="text-muted-foreground mb-1">Category</div>
<div className="font-medium">{tool.category}</div>
</div>
<div>
<div className="text-muted-foreground mb-1">Type</div>
<Badge variant="outline" className="capitalize">
{tool.type}
</Badge>
</div>
<div>
<div className="text-muted-foreground mb-1">Total Size</div>
<div className="font-medium">
{formatBytes(tool.files.reduce((acc, f) => acc + f.size, 0))}
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

123
web/components/footer.tsx Normal file
View File

@ -0,0 +1,123 @@
import Link from 'next/link'
import { Github, Heart, ExternalLink } from 'lucide-react'
export function Footer() {
const currentYear = new Date().getFullYear()
return (
<footer className="w-full border-t bg-background">
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
{/* About */}
<div className="space-y-3">
<h3 className="font-semibold text-lg">AI Prompts Explorer</h3>
<p className="text-sm text-muted-foreground">
The most comprehensive collection of AI tool system prompts and configurations.
</p>
<div className="flex items-center gap-4">
<Link
href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground transition-colors"
>
<Github className="w-5 h-5" />
</Link>
</div>
</div>
{/* Quick Links */}
<div className="space-y-3">
<h3 className="font-semibold">Quick Links</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>
<Link href="/" className="hover:text-foreground transition-colors">
Home
</Link>
</li>
<li>
<Link href="/browse" className="hover:text-foreground transition-colors">
Browse Tools
</Link>
</li>
<li>
<Link href="/stats" className="hover:text-foreground transition-colors">
Statistics
</Link>
</li>
<li>
<Link href="/compare" className="hover:text-foreground transition-colors">
Compare Tools
</Link>
</li>
</ul>
</div>
{/* Resources */}
<div className="space-y-3">
<h3 className="font-semibold">Resources</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>
<Link href="/about" className="hover:text-foreground transition-colors">
About
</Link>
</li>
<li>
<a
href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/main/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors flex items-center gap-1"
>
Contributing
<ExternalLink className="w-3 h-3" />
</a>
</li>
<li>
<a
href="https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools/blob/main/CHANGELOG.md"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors flex items-center gap-1"
>
Changelog
<ExternalLink className="w-3 h-3" />
</a>
</li>
<li>
<a
href="https://discord.gg/NwzrWErdMU"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors flex items-center gap-1"
>
Discord
<ExternalLink className="w-3 h-3" />
</a>
</li>
</ul>
</div>
{/* Stats */}
<div className="space-y-3">
<h3 className="font-semibold">Repository Stats</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>32+ AI Tools</li>
<li>96 Files</li>
<li>20,000+ Lines</li>
<li>11 Categories</li>
</ul>
</div>
</div>
<div className="mt-8 pt-8 border-t text-center text-sm text-muted-foreground">
<p className="flex items-center justify-center gap-1 flex-wrap">
Built with <Heart className="w-4 h-4 text-red-500 fill-red-500 inline" /> by the community
<span className="mx-1"></span>
© {currentYear} AI Prompts Explorer
</p>
</div>
</div>
</footer>
)
}

209
web/components/navbar.tsx Normal file
View File

@ -0,0 +1,209 @@
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { Search, Moon, Sun, Menu, X, Home, Grid, BarChart3, BookOpen, GitCompare } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { cn } from '@/lib/utils'
import { useTheme } from 'next-themes'
import { useState, useEffect } from 'react'
import { useAppStore } from '@/lib/store'
const navigation = [
{ name: 'Home', href: '/', icon: Home },
{ name: 'Browse', href: '/browse', icon: Grid },
{ name: 'Statistics', href: '/stats', icon: BarChart3 },
{ name: 'Compare', href: '/compare', icon: GitCompare },
{ name: 'About', href: '/about', icon: BookOpen },
]
export function Navbar() {
const pathname = usePathname()
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false)
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const [searchQuery, setSearchQuery] = useState('')
const { filters, setFilters } = useAppStore()
const comparison = useAppStore((state) => state.comparison)
useEffect(() => {
setMounted(true)
}, [])
const handleSearch = (e: React.FormEvent) => {
e.preventDefault()
setFilters({ search: searchQuery })
if (pathname !== '/browse') {
window.location.href = '/browse'
}
}
return (
<nav className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto px-4">
<div className="flex h-16 items-center justify-between">
{/* Logo */}
<Link href="/" className="flex items-center space-x-2 group">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-purple-500 rounded-lg blur opacity-75 group-hover:opacity-100 transition-opacity" />
<div className="relative bg-gradient-to-r from-blue-500 to-purple-500 p-2 rounded-lg">
<span className="text-white font-bold text-xl">AI</span>
</div>
</div>
<span className="hidden sm:inline-block font-bold text-xl bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
Prompts Explorer
</span>
</Link>
{/* Desktop Navigation */}
<div className="hidden md:flex items-center space-x-1">
{navigation.map((item) => {
const Icon = item.icon
const isActive = pathname === item.href
return (
<Link
key={item.name}
href={item.href}
className={cn(
'flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-all',
isActive
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
)}
>
<Icon className="w-4 h-4" />
{item.name}
{item.name === 'Compare' && comparison.length > 0 && (
<span className="ml-1 bg-destructive text-destructive-foreground text-xs rounded-full px-1.5 py-0.5">
{comparison.length}
</span>
)}
</Link>
)
})}
</div>
{/* Search Bar */}
<form onSubmit={handleSearch} className="hidden lg:flex items-center flex-1 max-w-md mx-8">
<div className="relative w-full">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search AI tools..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 pr-4"
/>
</div>
</form>
{/* Right Actions */}
<div className="flex items-center gap-2">
{/* Theme Toggle */}
{mounted && (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="hidden sm:inline-flex"
>
{theme === 'dark' ? (
<Sun className="h-5 w-5" />
) : (
<Moon className="h-5 w-5" />
)}
<span className="sr-only">Toggle theme</span>
</Button>
)}
{/* Mobile Menu Button */}
<Button
variant="ghost"
size="icon"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden"
>
{mobileMenuOpen ? (
<X className="h-5 w-5" />
) : (
<Menu className="h-5 w-5" />
)}
</Button>
</div>
</div>
{/* Mobile Menu */}
{mobileMenuOpen && (
<div className="md:hidden border-t py-4 space-y-4 animate-slide-down">
{/* Mobile Search */}
<form onSubmit={handleSearch} className="px-2">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search AI tools..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
</form>
{/* Mobile Navigation Links */}
<div className="space-y-1 px-2">
{navigation.map((item) => {
const Icon = item.icon
const isActive = pathname === item.href
return (
<Link
key={item.name}
href={item.href}
onClick={() => setMobileMenuOpen(false)}
className={cn(
'flex items-center gap-3 px-3 py-2 rounded-md text-base font-medium transition-all',
isActive
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:text-foreground hover:bg-muted'
)}
>
<Icon className="w-5 h-5" />
{item.name}
{item.name === 'Compare' && comparison.length > 0 && (
<span className="ml-auto bg-destructive text-destructive-foreground text-xs rounded-full px-2 py-0.5">
{comparison.length}
</span>
)}
</Link>
)
})}
</div>
{/* Mobile Theme Toggle */}
{mounted && (
<div className="px-2 pt-2 border-t">
<Button
variant="outline"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className="w-full justify-start"
>
{theme === 'dark' ? (
<>
<Sun className="mr-2 h-5 w-5" />
Light Mode
</>
) : (
<>
<Moon className="mr-2 h-5 w-5" />
Dark Mode
</>
)}
</Button>
</div>
)}
</div>
)}
</div>
</nav>
)
}

View File

@ -0,0 +1,9 @@
'use client'
import * as React from 'react'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import { type ThemeProviderProps } from 'next-themes/dist/types'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View File

@ -0,0 +1,170 @@
'use client'
import Link from 'next/link'
import { Heart, GitCompare, ExternalLink, FileText, Code } from 'lucide-react'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { AITool } from '@/lib/types'
import { getCategoryIcon, getCategoryColor } from '@/lib/data'
import { useAppStore } from '@/lib/store'
import { formatNumber, slugify } from '@/lib/utils'
import { cn } from '@/lib/utils'
interface ToolCardProps {
tool: AITool
variant?: 'default' | 'compact'
}
export function ToolCard({ tool, variant = 'default' }: ToolCardProps) {
const { favorites, addFavorite, removeFavorite, isFavorite } = useAppStore()
const { comparison, addToComparison, removeFromComparison, isInComparison } = useAppStore()
const favorite = isFavorite(tool.directory)
const inComparison = isInComparison(tool.directory)
const toggleFavorite = (e: React.MouseEvent) => {
e.preventDefault()
if (favorite) {
removeFavorite(tool.directory)
} else {
addFavorite(tool.directory)
}
}
const toggleComparison = (e: React.MouseEvent) => {
e.preventDefault()
if (inComparison) {
removeFromComparison(tool.directory)
} else if (comparison.length < 4) {
addToComparison(tool.directory)
}
}
if (variant === 'compact') {
return (
<Link href={`/tool/${slugify(tool.directory)}`}>
<Card className="h-full hover:shadow-lg transition-all cursor-pointer group">
<CardHeader className="pb-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<CardTitle className="text-lg line-clamp-1 group-hover:text-primary transition-colors">
{tool.name}
</CardTitle>
<CardDescription className="text-xs line-clamp-1 mt-1">
{tool.company}
</CardDescription>
</div>
<div className={cn('text-2xl flex-shrink-0', getCategoryColor(tool.category))}>
{getCategoryIcon(tool.category)}
</div>
</div>
</CardHeader>
<CardContent className="pb-3">
<div className="flex flex-wrap gap-1">
<Badge variant="secondary" className="text-xs">
{tool.category}
</Badge>
{tool.models.slice(0, 1).map((model) => (
<Badge key={model} variant="outline" className="text-xs">
{model}
</Badge>
))}
</div>
</CardContent>
</Card>
</Link>
)
}
return (
<Card className="h-full flex flex-col hover:shadow-lg transition-all group">
<CardHeader>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<div className="text-3xl">{getCategoryIcon(tool.category)}</div>
<Badge variant="secondary">{tool.category}</Badge>
</div>
<CardTitle className="text-xl line-clamp-1 group-hover:text-primary transition-colors">
{tool.name}
</CardTitle>
<CardDescription className="line-clamp-1 mt-1">
{tool.company}
</CardDescription>
</div>
<div className="flex gap-1 flex-shrink-0">
<Button
size="icon"
variant="ghost"
onClick={toggleFavorite}
className={cn('h-8 w-8', favorite && 'text-red-500')}
>
<Heart className={cn('w-4 h-4', favorite && 'fill-current')} />
</Button>
<Button
size="icon"
variant="ghost"
onClick={toggleComparison}
className={cn('h-8 w-8', inComparison && 'text-blue-500')}
disabled={!inComparison && comparison.length >= 4}
>
<GitCompare className={cn('w-4 h-4', inComparison && 'fill-current')} />
</Button>
</div>
</div>
</CardHeader>
<CardContent className="flex-1">
<p className="text-sm text-muted-foreground line-clamp-3 mb-4">
{tool.description}
</p>
<div className="space-y-2">
{/* Models */}
{tool.models.length > 0 && (
<div className="flex flex-wrap gap-1">
{tool.models.slice(0, 3).map((model) => (
<Badge key={model} variant="outline" className="text-xs">
{model}
</Badge>
))}
{tool.models.length > 3 && (
<Badge variant="outline" className="text-xs">
+{tool.models.length - 3} more
</Badge>
)}
</div>
)}
{/* Stats */}
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<FileText className="w-3 h-3" />
{tool.file_count} files
</div>
<div className="flex items-center gap-1">
<Code className="w-3 h-3" />
{formatNumber(tool.total_lines)} lines
</div>
</div>
</div>
</CardContent>
<CardFooter className="flex gap-2">
<Button asChild className="flex-1">
<Link href={`/tool/${slugify(tool.directory)}`}>
View Details
</Link>
</Button>
{tool.website && (
<Button asChild variant="outline" size="icon">
<a href={tool.website} target="_blank" rel="noopener noreferrer">
<ExternalLink className="w-4 h-4" />
</a>
</Button>
)}
</CardFooter>
</Card>
)
}

View File

@ -0,0 +1,29 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'default' | 'secondary' | 'destructive' | 'outline'
}
function Badge({ className, variant = 'default', ...props }: BadgeProps) {
return (
<div
className={cn(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80':
variant === 'default',
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80':
variant === 'secondary',
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80':
variant === 'destructive',
'text-foreground': variant === 'outline',
},
className
)}
{...props}
/>
)
}
export { Badge }

View File

@ -0,0 +1,44 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'
size?: 'default' | 'sm' | 'lg' | 'icon'
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'default', size = 'default', ...props }, ref) => {
return (
<button
className={cn(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
'bg-primary text-primary-foreground hover:bg-primary/90 active:scale-95':
variant === 'default',
'bg-destructive text-destructive-foreground hover:bg-destructive/90':
variant === 'destructive',
'border border-input bg-background hover:bg-accent hover:text-accent-foreground':
variant === 'outline',
'bg-secondary text-secondary-foreground hover:bg-secondary/80':
variant === 'secondary',
'hover:bg-accent hover:text-accent-foreground': variant === 'ghost',
'text-primary underline-offset-4 hover:underline': variant === 'link',
},
{
'h-10 px-4 py-2': size === 'default',
'h-9 rounded-md px-3': size === 'sm',
'h-11 rounded-md px-8': size === 'lg',
'h-10 w-10': size === 'icon',
},
className
)}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = 'Button'
export { Button }

View File

@ -0,0 +1,78 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-lg border bg-card text-card-foreground shadow-sm transition-all hover:shadow-md',
className
)}
{...props}
/>
))
Card.displayName = 'Card'
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
))
CardHeader.displayName = 'CardHeader'
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
'text-2xl font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
))
CardTitle.displayName = 'CardTitle'
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
))
CardDescription.displayName = 'CardDescription'
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
))
CardContent.displayName = 'CardContent'
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
))
CardFooter.displayName = 'CardFooter'
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@ -0,0 +1,24 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = 'Input'
export { Input }

1286
web/data/index.json Normal file

File diff suppressed because it is too large Load Diff

101
web/lib/data.ts Normal file
View File

@ -0,0 +1,101 @@
import { AITool, ToolIndex } from './types'
import indexData from '../data/index.json'
export function getToolIndex(): ToolIndex {
return indexData as ToolIndex
}
export function getAllTools(): AITool[] {
const index = getToolIndex()
return index.tools
}
export function getToolByDirectory(directory: string): AITool | undefined {
const tools = getAllTools()
return tools.find(tool => tool.directory === directory)
}
export function getToolsByCategory(category: string): AITool[] {
const tools = getAllTools()
return tools.filter(tool => tool.category === category)
}
export function getToolsByCompany(company: string): AITool[] {
const tools = getAllTools()
return tools.filter(tool => tool.company === company)
}
export function searchTools(query: string): AITool[] {
if (!query) return getAllTools()
const lowercaseQuery = query.toLowerCase()
const tools = getAllTools()
return tools.filter(tool => {
return (
tool.name.toLowerCase().includes(lowercaseQuery) ||
tool.description.toLowerCase().includes(lowercaseQuery) ||
tool.company.toLowerCase().includes(lowercaseQuery) ||
tool.category.toLowerCase().includes(lowercaseQuery) ||
tool.models.some(model => model.toLowerCase().includes(lowercaseQuery))
)
})
}
export function getCategories(): string[] {
const index = getToolIndex()
return Object.keys(index.stats.by_category).sort()
}
export function getCompanies(): string[] {
const tools = getAllTools()
const companies = new Set(tools.map(tool => tool.company))
return Array.from(companies).sort()
}
export function getModels(): string[] {
const tools = getAllTools()
const models = new Set<string>()
tools.forEach(tool => {
tool.models.forEach(model => models.add(model))
})
return Array.from(models).sort()
}
export function getStats() {
const index = getToolIndex()
return index.stats
}
export function getCategoryIcon(category: string): string {
const icons: Record<string, string> = {
'Code Assistant': '💻',
'IDE': '🏢',
'AI Agent': '🤖',
'Web Builder': '🌐',
'Terminal': '🖥️',
'Cloud IDE': '🏭',
'Document Assistant': '📝',
'Search Assistant': '🔍',
'Foundation Model': '🧠',
'Collection': '📚',
'Unknown': '❓',
}
return icons[category] || '📦'
}
export function getCategoryColor(category: string): string {
const colors: Record<string, string> = {
'Code Assistant': 'from-blue-500 to-cyan-500',
'IDE': 'from-purple-500 to-pink-500',
'AI Agent': 'from-green-500 to-emerald-500',
'Web Builder': 'from-orange-500 to-red-500',
'Terminal': 'from-gray-500 to-slate-500',
'Cloud IDE': 'from-indigo-500 to-blue-500',
'Document Assistant': 'from-yellow-500 to-orange-500',
'Search Assistant': 'from-pink-500 to-rose-500',
'Foundation Model': 'from-violet-500 to-purple-500',
'Collection': 'from-teal-500 to-cyan-500',
}
return colors[category] || 'from-gray-500 to-slate-500'
}

89
web/lib/store.ts Normal file
View File

@ -0,0 +1,89 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import { AITool, FilterState } from './types'
interface AppState {
// Favorites
favorites: string[]
addFavorite: (toolDirectory: string) => void
removeFavorite: (toolDirectory: string) => void
isFavorite: (toolDirectory: string) => boolean
// Comparison
comparison: string[]
addToComparison: (toolDirectory: string) => void
removeFromComparison: (toolDirectory: string) => void
clearComparison: () => void
isInComparison: (toolDirectory: string) => boolean
// Filters
filters: FilterState
setFilters: (filters: Partial<FilterState>) => void
resetFilters: () => void
// View preferences
viewMode: 'grid' | 'list'
setViewMode: (mode: 'grid' | 'list') => void
}
const defaultFilters: FilterState = {
search: '',
categories: [],
types: [],
companies: [],
models: [],
sortBy: 'name',
sortOrder: 'asc',
}
export const useAppStore = create<AppState>()(
persist(
(set, get) => ({
// Favorites
favorites: [],
addFavorite: (toolDirectory) =>
set((state) => ({
favorites: [...state.favorites, toolDirectory],
})),
removeFavorite: (toolDirectory) =>
set((state) => ({
favorites: state.favorites.filter((dir) => dir !== toolDirectory),
})),
isFavorite: (toolDirectory) => get().favorites.includes(toolDirectory),
// Comparison
comparison: [],
addToComparison: (toolDirectory) =>
set((state) => {
if (state.comparison.length >= 4) return state
return { comparison: [...state.comparison, toolDirectory] }
}),
removeFromComparison: (toolDirectory) =>
set((state) => ({
comparison: state.comparison.filter((dir) => dir !== toolDirectory),
})),
clearComparison: () => set({ comparison: [] }),
isInComparison: (toolDirectory) =>
get().comparison.includes(toolDirectory),
// Filters
filters: defaultFilters,
setFilters: (filters) =>
set((state) => ({
filters: { ...state.filters, ...filters },
})),
resetFilters: () => set({ filters: defaultFilters }),
// View preferences
viewMode: 'grid',
setViewMode: (mode) => set({ viewMode: mode }),
}),
{
name: 'ai-prompts-storage',
partialize: (state) => ({
favorites: state.favorites,
viewMode: state.viewMode,
}),
}
)
)

60
web/lib/types.ts Normal file
View File

@ -0,0 +1,60 @@
export interface AITool {
name: string
company: string
category: string
type: 'proprietary' | 'open-source' | 'unknown'
description: string
website: string | null
models: string[]
directory: string
files: ToolFile[]
file_count: number
total_lines: number
subcategories?: string[]
}
export interface ToolFile {
name: string
path: string
size: number
type: string
lines?: number
modified?: string
}
export interface ToolIndex {
generated: string
repository: string
version: string
stats: {
total_tools: number
total_files: number
total_lines: number
by_category: Record<string, number>
by_type: Record<string, number>
}
tools: AITool[]
}
export type Category =
| 'Code Assistant'
| 'IDE'
| 'AI Agent'
| 'Web Builder'
| 'Terminal'
| 'Cloud IDE'
| 'Document Assistant'
| 'Search Assistant'
| 'Foundation Model'
| 'Collection'
| 'Unknown'
export interface FilterState {
search: string
categories: Category[]
types: ('proprietary' | 'open-source')[]
companies: string[]
models: string[]
sortBy: 'name' | 'lines' | 'files' | 'company'
sortOrder: 'asc' | 'desc'
}

43
web/lib/utils.ts Normal file
View File

@ -0,0 +1,43 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function formatNumber(num: number): string {
return new Intl.NumberFormat('en-US').format(num)
}
export function formatBytes(bytes: number): string {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]
}
export function slugify(str: string): string {
return str
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim()
}
export function truncate(str: string, length: number): string {
if (str.length <= length) return str
return str.slice(0, length) + '...'
}
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null
return (...args: Parameters<T>) => {
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => func(...args), wait)
}
}

22
web/next.config.ts Normal file
View File

@ -0,0 +1,22 @@
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactStrictMode: true,
poweredByHeader: false,
experimental: {
optimizePackageImports: ['lucide-react'],
},
async redirects() {
return [
{
source: '/tools',
destination: '/browse',
permanent: true,
},
]
},
}
export default nextConfig

40
web/package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "ai-prompts-explorer",
"version": "2.0.0",
"description": "Modern web interface for exploring AI tool system prompts",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"type-check": "tsc --noEmit"
},
"dependencies": {
"next": "^15.0.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"framer-motion": "^11.5.4",
"lucide-react": "^0.446.0",
"clsx": "^2.1.1",
"tailwind-merge": "^2.5.2",
"next-themes": "^0.3.0",
"zustand": "^4.5.5",
"react-wrap-balancer": "^1.1.1"
},
"devDependencies": {
"@types/node": "^22.7.5",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"typescript": "^5.6.3",
"tailwindcss": "^3.4.13",
"postcss": "^8.4.47",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint-config-next": "^15.0.3"
},
"engines": {
"node": ">=22.0.0",
"npm": ">=10.0.0"
}
}

9
web/postcss.config.mjs Normal file
View File

@ -0,0 +1,9 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
export default config

62
web/setup.sh Executable file
View File

@ -0,0 +1,62 @@
#!/bin/bash
# AI Prompts Explorer - Web Setup Script
# This script sets up the Next.js web application
set -e
echo "🚀 AI Prompts Explorer - Web Setup"
echo "=================================="
echo ""
# Check Node version
echo "📦 Checking Node.js version..."
NODE_VERSION=$(node -v)
echo "Found Node.js: $NODE_VERSION"
if [[ ! "$NODE_VERSION" =~ ^v(22|23|24|25) ]]; then
echo "⚠️ Warning: Node.js 22+ is recommended (found $NODE_VERSION)"
fi
echo ""
# Check if we're in the web directory
if [ ! -f "package.json" ]; then
echo "❌ Error: package.json not found"
echo "Please run this script from the web/ directory"
exit 1
fi
# Install dependencies
echo "📥 Installing dependencies..."
npm install
echo ""
# Generate metadata if needed
if [ ! -f "data/index.json" ]; then
echo "📊 Generating metadata..."
cd ..
python3 scripts/generate_metadata.py
mkdir -p web/data
cp scripts/index.json web/data/
cd web
echo "✓ Metadata generated"
else
echo "✓ Metadata already exists"
fi
echo ""
echo "✅ Setup complete!"
echo ""
echo "🎉 You can now run the development server:"
echo " npm run dev"
echo ""
echo "📖 The app will be available at:"
echo " http://localhost:3000"
echo ""
echo "📚 Other commands:"
echo " npm run build - Build for production"
echo " npm start - Start production server"
echo " npm run lint - Lint code"
echo ""

86
web/tailwind.config.ts Normal file
View File

@ -0,0 +1,86 @@
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
darkMode: 'class',
theme: {
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
animation: {
'fade-in': 'fade-in 0.5s ease-in-out',
'slide-in': 'slide-in 0.3s ease-out',
'slide-up': 'slide-up 0.3s ease-out',
'scale-in': 'scale-in 0.2s ease-out',
'shimmer': 'shimmer 2s linear infinite',
},
keyframes: {
'fade-in': {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
'slide-in': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(0)' },
},
'slide-up': {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
'scale-in': {
'0%': { transform: 'scale(0.95)', opacity: '0' },
'100%': { transform: 'scale(1)', opacity: '1' },
},
'shimmer': {
'0%': { backgroundPosition: '-1000px 0' },
'100%': { backgroundPosition: '1000px 0' },
},
},
},
},
plugins: [],
}
export default config

27
web/tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}