"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; }; // Pagination State const [currentPage, setCurrentPage] = useState(1); const [itemsPerPage, setItemsPerPage] = useState(24); // Reset pagination when filters change useEffect(() => { setCurrentPage(1); }, [searchTerm, selectedCategory, selectedSource, sortMode]); const finalPrompts = displayPrompts(); // Pagination Logic const totalPages = Math.ceil(finalPrompts.length / itemsPerPage); const paginatedPrompts = finalPrompts.slice( (currentPage - 1) * itemsPerPage, currentPage * itemsPerPage ); 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 */} {sortMode === 'all' && (
{(() => { const priority = ['NAM', 'NỮ', 'SINH NHẬT', 'HALLOWEEN', 'NOEL', 'NEW YEAR', 'TRẺ EM', 'COUPLE', 'CHA - MẸ', 'MẸ BẦU', 'ĐẶC BIỆT']; // Sort uniqueCategories (which only contains categories that exist in data) const sortedCategories = uniqueCategories.sort((a, b) => { if (a === 'All') return -1; if (b === 'All') return 1; const idxA = priority.indexOf(a); const idxB = priority.indexOf(b); if (idxA !== -1 && idxB !== -1) return idxA - idxB; if (idxA !== -1) return -1; if (idxB !== -1) return 1; return a.localeCompare(b); }); return sortedCategories.map(cat => ( )); })()}
)} {/* Source Filter */}
Sources: {uniqueSources.map(source => ( ))}
{/* Top Pagination Controls */} {!loading && totalPages > 1 && (
Page {currentPage} of {totalPages}
)} {loading && !prompts.length ? (
) : ( <>
{paginatedPrompts.map((p) => ( {p.images && p.images.length > 0 ? (
{p.title}
) : (
)}

{p.title}

{p.source}

{p.prompt}

))}
{/* Pagination Controls */} {totalPages > 1 && (
Show: {[24, 48, 96].map(size => ( ))}
Page {currentPage} of {totalPages}
)} )} {!loading && finalPrompts.length === 0 && (
{sortMode === 'history' ? "No prompts used yet." : "No prompts found."}
)}
); }