mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2025-12-16 05:25:11 +00:00
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:
parent
cb8bb88b4f
commit
9d5ee88ea3
47
README.md
47
README.md
@ -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
3
web/.eslintrc.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
37
web/.gitignore
vendored
Normal file
37
web/.gitignore
vendored
Normal 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
237
web/README.md
Normal 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.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## ✨ 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)
|
||||||
|
|
||||||
|
[](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
211
web/app/about/page.tsx
Normal 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
265
web/app/browse/page.tsx
Normal 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
203
web/app/compare/page.tsx
Normal 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
109
web/app/globals.css
Normal 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
45
web/app/layout.tsx
Normal 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
13
web/app/loading.tsx
Normal 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
34
web/app/not-found.tsx
Normal 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
217
web/app/page.tsx
Normal 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
210
web/app/stats/page.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
225
web/app/tool/[slug]/page.tsx
Normal file
225
web/app/tool/[slug]/page.tsx
Normal 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
123
web/components/footer.tsx
Normal 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
209
web/components/navbar.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
web/components/theme-provider.tsx
Normal file
9
web/components/theme-provider.tsx
Normal 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>
|
||||||
|
}
|
||||||
170
web/components/tool-card.tsx
Normal file
170
web/components/tool-card.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
web/components/ui/badge.tsx
Normal file
29
web/components/ui/badge.tsx
Normal 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 }
|
||||||
44
web/components/ui/button.tsx
Normal file
44
web/components/ui/button.tsx
Normal 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 }
|
||||||
78
web/components/ui/card.tsx
Normal file
78
web/components/ui/card.tsx
Normal 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 }
|
||||||
24
web/components/ui/input.tsx
Normal file
24
web/components/ui/input.tsx
Normal 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
1286
web/data/index.json
Normal file
File diff suppressed because it is too large
Load Diff
101
web/lib/data.ts
Normal file
101
web/lib/data.ts
Normal 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
89
web/lib/store.ts
Normal 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
60
web/lib/types.ts
Normal 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
43
web/lib/utils.ts
Normal 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
22
web/next.config.ts
Normal 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
40
web/package.json
Normal 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
9
web/postcss.config.mjs
Normal 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
62
web/setup.sh
Executable 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
86
web/tailwind.config.ts
Normal 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
27
web/tsconfig.json
Normal 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"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user