open-prompt / src /lib /search.ts
GitHub Action
Automated sync to Hugging Face
bcce530
/**
* 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)
}
}