"use client"; import React, { useEffect, useState } from 'react'; import { useStore } from '@/lib/store'; import { Copy, Sparkles, RefreshCw, Loader2, Image as ImageIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Prompt, PromptCache } from '@/lib/types'; import { motion, AnimatePresence } from 'framer-motion'; export function PromptLibrary({ onSelect }: { onSelect?: (prompt: string) => void }) { const { setPrompt, settings } = useStore(); const [prompts, setPrompts] = useState([]); const [loading, setLoading] = useState(true); const [generating, setGenerating] = useState(false); const [selectedCategory, setSelectedCategory] = useState('All'); const [selectedSource, setSelectedSource] = useState('All'); const [searchTerm, setSearchTerm] = useState(''); const [sortMode, setSortMode] = useState<'all' | 'latest' | 'history' | 'foryou'>('all'); const fetchPrompts = async () => { setLoading(true); try { const res = await fetch('/api/prompts'); if (res.ok) { const data: PromptCache = await res.json(); setPrompts(data.prompts); } } catch (error) { console.error("Failed to fetch prompts", error); } finally { setLoading(false); } }; const syncPrompts = async () => { setLoading(true); try { const syncRes = await fetch('/api/prompts/sync', { method: 'POST' }); if (!syncRes.ok) throw new Error('Sync failed'); await fetchPrompts(); } catch (error) { console.error("Failed to sync prompts", error); } finally { setLoading(false); } }; const generateMissingPreviews = async () => { if (!settings.whiskCookies) { alert("Please set Whisk Cookies in Settings first!"); return; } setGenerating(true); try { // Find prompts without images const missing = prompts.filter(p => !p.images || p.images.length === 0); console.log(`Found ${missing.length} prompts without images.`); for (const prompt of missing) { try { console.log(`Requesting preview for: ${prompt.title}`); const res = await fetch('/api/prompts/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: prompt.id, prompt: prompt.prompt, cookies: settings.whiskCookies }) }); if (res.ok) { const { url } = await res.json(); setPrompts(prev => prev.map(p => p.id === prompt.id ? { ...p, images: [url, ...(p.images || [])] } : p )); } else { const err = await res.json(); if (res.status === 422) { console.warn(`Skipped unsafe prompt "${prompt.title}": ${err.error}`); } else { console.error('API Error:', err); } } // Delay is still good to prevent flooding backend/google await new Promise(r => setTimeout(r, 2000)); } catch (error) { console.error(`Failed to generate for ${prompt.id}:`, error); } } } catch (e) { console.error("Generation process failed:", e); } finally { setGenerating(false); } }; useEffect(() => { fetchPrompts(); }, []); const handleSelect = async (p: Prompt) => { setPrompt(p.prompt); if (onSelect) onSelect(p.prompt); // Track usage try { await fetch('/api/prompts/use', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: p.id }) }); // Optimistic update setPrompts(prev => prev.map(item => item.id === p.id ? { ...item, useCount: (item.useCount || 0) + 1, lastUsedAt: Date.now() } : item )); } catch (e) { console.error("Failed to track usage", e); } }; // Derived State const filteredPrompts = prompts.filter(p => { const matchesSearch = p.title.toLowerCase().includes(searchTerm.toLowerCase()) || p.prompt.toLowerCase().includes(searchTerm.toLowerCase()); const matchesCategory = selectedCategory === 'All' || p.category === selectedCategory; const matchesSource = selectedSource === 'All' || p.source === selectedSource; return matchesSearch && matchesCategory && matchesSource; }).sort((a, b) => { if (sortMode === 'latest') { return (b.createdAt || 0) - (a.createdAt || 0); } if (sortMode === 'history') { return (b.lastUsedAt || 0) - (a.lastUsedAt || 0); } return 0; // Default order (or ID) }); const displayPrompts = () => { if (sortMode === 'history') { return filteredPrompts.filter(p => (p.useCount || 0) > 0); } if (sortMode === 'foryou') { // Calculate top categories const categoryCounts: Record = {}; prompts.filter(p => (p.useCount || 0) > 0).forEach(p => { categoryCounts[p.category] = (categoryCounts[p.category] || 0) + 1; }); const topCategories = Object.entries(categoryCounts) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([cat]) => cat); if (topCategories.length === 0) return filteredPrompts; // No history yet return filteredPrompts.filter(p => topCategories.includes(p.category)); } return filteredPrompts; }; const finalPrompts = displayPrompts(); const uniqueCategories = ['All', ...Array.from(new Set(prompts.map(p => p.category)))].filter(Boolean); const uniqueSources = ['All', ...Array.from(new Set(prompts.map(p => p.source)))].filter(Boolean); return (

Prompt Library

Curated inspiration from the community.

setSearchTerm(e.target.value)} />
{generating && (
Generating preview images for library prompts... This may take a while.
)} {/* Smart Tabs */}
{(['all', 'latest', 'history', 'foryou'] as const).map(mode => ( ))}
{/* Sub-Categories (only show if NOT history/foryou to keep clean? Or keep it?) */} {sortMode === 'all' && (
{uniqueCategories.map(cat => ( ))}
)} {/* Source Filter */}
Sources: {uniqueSources.map(source => ( ))}
{loading && !prompts.length ? (
) : (
{finalPrompts.map((p) => ( {p.images && p.images.length > 0 ? (
{p.title}
) : (
)}

{p.title}

{p.source}

{p.prompt}

))}
)} {!loading && finalPrompts.length === 0 && (
{sortMode === 'history' ? "No prompts used yet." : "No prompts found."}
)}
); }