File size: 4,463 Bytes
bcce530
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
 * Search utilities for OpenPrompt
 * Supports full-text search across prompts
 */

export interface SearchFilters {
    query?: string
    category?: string
    tags?: string[]
    model?: string
    minStars?: number
    minRuns?: number
    dateRange?: 'day' | 'week' | 'month' | 'all'
    sortBy?: 'trending' | 'recent' | 'stars' | 'runs'
}

export interface SearchResult {
    id: string
    slug: string
    title: string
    description: string | null
    category: string | null
    tags: string[]
    totalRuns: number
    starsCount: number
    remixesCount: number
    createdAt: Date
    creator: {
        name: string | null
        username: string | null
        image: string | null
    } | null
    score?: number // relevance score
}

/**
 * Build Prisma where clause from search filters
 */
export function buildSearchWhere(filters: SearchFilters) {
    const where: Record<string, unknown> = {
        visibility: 'public',
    }

    // Text search (title + description)
    if (filters.query) {
        where.OR = [
            { title: { contains: filters.query, mode: 'insensitive' } },
            { description: { contains: filters.query, mode: 'insensitive' } },
            { tags: { hasSome: [filters.query.toLowerCase()] } },
        ]
    }

    // Category filter
    if (filters.category) {
        where.category = filters.category
    }

    // Tags filter
    if (filters.tags && filters.tags.length > 0) {
        where.tags = { hasSome: filters.tags }
    }

    // Model filter
    if (filters.model) {
        where.modelAllowed = { has: filters.model }
    }

    // Stars filter
    if (filters.minStars) {
        where.starsCount = { gte: filters.minStars }
    }

    // Runs filter
    if (filters.minRuns) {
        where.totalRuns = { gte: filters.minRuns }
    }

    // Date range filter
    if (filters.dateRange && filters.dateRange !== 'all') {
        const now = new Date()
        const cutoff = new Date()

        switch (filters.dateRange) {
            case 'day':
                cutoff.setDate(now.getDate() - 1)
                break
            case 'week':
                cutoff.setDate(now.getDate() - 7)
                break
            case 'month':
                cutoff.setMonth(now.getMonth() - 1)
                break
        }

        where.createdAt = { gte: cutoff }
    }

    return where
}

/**
 * Build Prisma orderBy from sort option
 */
export function buildSearchOrderBy(sortBy: SearchFilters['sortBy'] = 'trending') {
    switch (sortBy) {
        case 'recent':
            return [{ createdAt: 'desc' as const }]
        case 'stars':
            return [{ starsCount: 'desc' as const }, { createdAt: 'desc' as const }]
        case 'runs':
            return [{ totalRuns: 'desc' as const }, { createdAt: 'desc' as const }]
        case 'trending':
        default:
            // Trending uses a calculated score (handled separately)
            return [{ totalRuns: 'desc' as const }, { starsCount: 'desc' as const }]
    }
}

/**
 * Extract unique categories from prompts
 */
export function getCategories(): string[] {
    return [
        'Content',
        'Development',
        'Marketing',
        'Business',
        'Education',
        'Creative',
        'Research',
    ]
}

/**
 * Extract unique tags from prompts
 */
export async function getPopularTags(limit: number = 20): Promise<string[]> {
    try {
        // Query database for actual popular tags
        const { prisma } = await import('@/lib/prisma')
        const prompts = await prisma.prompt.findMany({
            where: { visibility: 'public', tags: { isEmpty: false } },
            select: { tags: true },
            orderBy: { totalRuns: 'desc' },
            take: 200,
        })

        // Count tag frequency
        const tagCounts = new Map<string, number>()
        for (const prompt of prompts) {
            for (const tag of prompt.tags) {
                tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1)
            }
        }

        // Sort by frequency and return top N
        return [...tagCounts.entries()]
            .sort((a, b) => b[1] - a[1])
            .slice(0, limit)
            .map(([tag]) => tag)
    } catch {
        // Fallback to hardcoded list if DB query fails
        return [
            'blog', 'writing', 'code', 'email', 'social-media',
            'seo', 'ai', 'productivity', 'marketing', 'business',
        ].slice(0, limit)
    }
}