Update application code and dependencies
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
import { prisma } from "@/app/lib/prisma"
|
||||
import Link from "next/link"
|
||||
|
||||
function buildQueryString(params: Record<string, string>) {
|
||||
const sp = new URLSearchParams()
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value) sp.append(key, value)
|
||||
})
|
||||
const qs = sp.toString()
|
||||
return qs ? `?${qs}` : ""
|
||||
}
|
||||
|
||||
export default async function AgentsPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ category?: string; q?: string; sort?: string; page?: string }>
|
||||
}) {
|
||||
const params = await searchParams
|
||||
const { category, q, sort, page } = await searchParams
|
||||
const categoryId = category ? parseInt(category) : undefined
|
||||
const searchQuery = q || ""
|
||||
const sortBy = sort || ""
|
||||
const currentPage = page ? parseInt(page) : 1
|
||||
const pageSize = 9
|
||||
|
||||
const where: any = {}
|
||||
if (categoryId) where.categoryId = categoryId
|
||||
if (searchQuery) {
|
||||
where.OR = [
|
||||
{ name: { contains: searchQuery } },
|
||||
{ description: { contains: searchQuery } },
|
||||
]
|
||||
}
|
||||
|
||||
let orderBy: any = { createdAt: "desc" }
|
||||
if (sortBy === "popular") orderBy = { usageCount: "desc" }
|
||||
|
||||
const totalAgents = await prisma.agent.count({ where })
|
||||
const totalPages = Math.ceil(totalAgents / pageSize)
|
||||
|
||||
const agents = await prisma.agent.findMany({
|
||||
where,
|
||||
include: { category: true },
|
||||
orderBy,
|
||||
skip: (currentPage - 1) * pageSize,
|
||||
take: pageSize,
|
||||
})
|
||||
|
||||
const popularAgents = await prisma.agent.findMany({
|
||||
include: { category: true },
|
||||
orderBy: { usageCount: "desc" },
|
||||
take: 6,
|
||||
})
|
||||
|
||||
const categories = await prisma.category.findMany({
|
||||
include: { _count: { select: { agents: true } } },
|
||||
})
|
||||
|
||||
const totalCategories = await prisma.category.count()
|
||||
|
||||
const getPageUrl = (page: number) => {
|
||||
const params: Record<string, string> = {}
|
||||
if (categoryId) params.category = categoryId.toString()
|
||||
if (searchQuery) params.q = searchQuery
|
||||
if (sortBy) params.sort = sortBy
|
||||
if (page > 1) params.page = page.toString()
|
||||
return `/agents${buildQueryString(params)}`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<nav className="bg-white border-b border-gray-200">
|
||||
<div className="max-w-6xl mx-auto px-6 py-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm">AI</span>
|
||||
</div>
|
||||
<span className="font-bold text-gray-900">冲浪智能体广场</span>
|
||||
</Link>
|
||||
<div className="flex items-center gap-6">
|
||||
<Link href="/" className="text-gray-600 hover:text-blue-600 transition">首页</Link>
|
||||
<Link href="/agents" className="text-blue-600 font-medium">智能体广场</Link>
|
||||
<Link href="/news" className="text-gray-600 hover:text-blue-600 transition">新闻动态</Link>
|
||||
<Link href="/admin/login" className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition">
|
||||
登录
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="pt-16">
|
||||
<div className="max-w-6xl mx-auto px-6 py-8">
|
||||
{/* 面包屑 */}
|
||||
<div className="flex items-center gap-2 mb-6 text-sm">
|
||||
<Link href="/" className="text-gray-500 hover:text-blue-600 transition">首页</Link>
|
||||
<span className="text-gray-400">/</span>
|
||||
<span className="text-gray-900 font-medium">智能体广场</span>
|
||||
</div>
|
||||
|
||||
{/* 页面标题 */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">智能体广场</h1>
|
||||
<p className="text-gray-500">浏览并搜索公司发布的 AI 智能体应用,找到最适合您的智能助手</p>
|
||||
</div>
|
||||
|
||||
{/* 统计信息 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||
<div className="bg-white rounded-2xl p-5 border border-gray-200 shadow-sm">
|
||||
<div className="text-2xl font-bold text-blue-600 mb-1">{totalAgents}</div>
|
||||
<div className="text-sm text-gray-500">智能体总数</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl p-5 border border-gray-200 shadow-sm">
|
||||
<div className="text-2xl font-bold text-green-600 mb-1">{totalCategories}</div>
|
||||
<div className="text-sm text-gray-500">分类数量</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl p-5 border border-gray-200 shadow-sm">
|
||||
<div className="text-2xl font-bold text-purple-600 mb-1">5000+</div>
|
||||
<div className="text-sm text-gray-500">活跃用户</div>
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl p-5 border border-gray-200 shadow-sm">
|
||||
<div className="text-2xl font-bold text-orange-600 mb-1">98%</div>
|
||||
<div className="text-sm text-gray-500">用户满意度</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索与筛选 */}
|
||||
<div className="bg-white rounded-2xl border border-gray-200 p-6 mb-8 shadow-sm">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">搜索与筛选</h3>
|
||||
<form className="flex flex-wrap gap-4 items-center">
|
||||
<div className="flex-1 min-w-64 relative">
|
||||
<svg className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||
</svg>
|
||||
<input
|
||||
name="q"
|
||||
type="text"
|
||||
defaultValue={searchQuery}
|
||||
placeholder="搜索智能体名称或简介..."
|
||||
className="w-full pl-12 pr-4 py-3 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition"
|
||||
/>
|
||||
</div>
|
||||
<select
|
||||
name="category"
|
||||
defaultValue={categoryId || ""}
|
||||
className="px-4 py-3 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
|
||||
>
|
||||
<option value="">全部分类</option>
|
||||
{categories.map((cat) => (
|
||||
<option key={cat.id} value={cat.id}>{cat.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
name="sort"
|
||||
defaultValue={sortBy}
|
||||
className="px-4 py-3 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
|
||||
>
|
||||
<option value="">默认排序</option>
|
||||
<option value="date">最新发布</option>
|
||||
<option value="popular">最受欢迎</option>
|
||||
</select>
|
||||
<button type="submit" className="px-6 py-3 bg-blue-600 text-white rounded-xl text-sm hover:bg-blue-700 transition">
|
||||
搜索
|
||||
</button>
|
||||
<Link href="/agents" className="px-4 py-3 bg-gray-100 text-gray-600 rounded-xl text-sm hover:bg-gray-200 transition">
|
||||
重置
|
||||
</Link>
|
||||
</form>
|
||||
<div className="flex flex-wrap gap-2 mt-4 pt-4 border-t border-gray-100">
|
||||
<span className="text-xs text-gray-400 self-center mr-2">快捷分类:</span>
|
||||
<Link href="/agents" className={`px-3 py-1.5 rounded-full text-xs border border-gray-200 bg-white text-gray-600 hover:border-blue-400 hover:text-blue-600 transition-all ${!categoryId ? "bg-blue-50 border-blue-400 text-blue-600" : ""}`}>
|
||||
全部
|
||||
</Link>
|
||||
{categories.map((cat) => (
|
||||
<Link
|
||||
key={cat.id}
|
||||
href={`/agents?category=${cat.id}`}
|
||||
className={`px-3 py-1.5 rounded-full text-xs border border-gray-200 bg-white text-gray-600 hover:border-blue-400 hover:text-blue-600 transition-all ${categoryId === cat.id ? "bg-blue-50 border-blue-400 text-blue-600" : ""}`}
|
||||
>
|
||||
{cat.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 热门推荐 */}
|
||||
{popularAgents.length > 0 && (
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">热门推荐</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||
{popularAgents.map((agent) => (
|
||||
<Link
|
||||
key={agent.id}
|
||||
href={`/agents/${agent.slug}`}
|
||||
className="bg-white rounded-2xl border border-gray-200 p-5 hover:border-blue-300 shadow-sm hover:shadow-md transition-all duration-300"
|
||||
>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="w-14 h-14 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-3xl flex-shrink-0">
|
||||
{agent.icon || "🤖"}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 truncate">{agent.name}</h3>
|
||||
{agent.category && (
|
||||
<span className="inline-block mt-1 px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-600">
|
||||
{agent.category.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm leading-relaxed mb-4">{agent.description}</p>
|
||||
<div className="flex items-center justify-between text-xs text-gray-400">
|
||||
<span>使用 {agent.usageCount} 次</span>
|
||||
<span className="text-blue-600 font-medium">查看详情 →</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 智能体列表头部 */}
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h2 className="text-xl font-bold text-gray-900">智能体列表</h2>
|
||||
<span className="text-sm text-gray-500">显示 {totalAgents} 个智能体</span>
|
||||
</div>
|
||||
|
||||
{/* 智能体列表 */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{agents.map((agent) => (
|
||||
<Link
|
||||
key={agent.id}
|
||||
href={`/agents/${agent.slug}`}
|
||||
className="bg-white rounded-2xl border border-gray-200 p-5 hover:border-blue-300 shadow-sm hover:shadow-md transition-all duration-300"
|
||||
>
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="w-14 h-14 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center text-3xl flex-shrink-0">
|
||||
{agent.icon || "🤖"}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-semibold text-gray-900 truncate">{agent.name}</h3>
|
||||
{agent.category && (
|
||||
<span className="inline-block mt-1 px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-600">
|
||||
{agent.category.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-gray-500 text-sm leading-relaxed mb-4">{agent.description}</p>
|
||||
<div className="flex items-center justify-between text-xs text-gray-400">
|
||||
<span>使用 {agent.usageCount} 次</span>
|
||||
<span className={`font-medium ${agent.status === "active" ? "text-green-600" : "text-gray-600"}`}>
|
||||
{agent.status === "active" ? "运行中" : agent.status}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 空状态 */}
|
||||
{agents.length === 0 && (
|
||||
<div className="text-center py-24 bg-gray-50 rounded-2xl">
|
||||
<div className="text-7xl mb-6">🔍</div>
|
||||
<h3 className="text-xl font-medium text-gray-700 mb-3">未找到匹配的智能体</h3>
|
||||
<p className="text-gray-400 mb-6 max-w-md mx-auto">试试调整筛选条件或关键词,或者浏览我们的热门推荐</p>
|
||||
<Link href="/agents" className="inline-block px-6 py-3 bg-blue-600 text-white rounded-lg text-sm hover:bg-blue-700 transition">
|
||||
重置筛选
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分页 */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between mt-8 px-2">
|
||||
<span className="text-sm text-gray-500">
|
||||
第 {currentPage} 页,共 {totalPages} 页
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href={getPageUrl(currentPage - 1)}
|
||||
className={`px-4 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition ${currentPage <= 1 ? "opacity-40 cursor-not-allowed" : ""}`}
|
||||
>
|
||||
上一页
|
||||
</Link>
|
||||
<div className="flex items-center gap-1">
|
||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map((pageNum) => (
|
||||
<Link
|
||||
key={pageNum}
|
||||
href={getPageUrl(pageNum)}
|
||||
className={`w-8 h-8 flex items-center justify-center text-sm rounded-lg transition ${currentPage === pageNum ? "bg-blue-600 text-white" : "hover:bg-gray-50 text-gray-600"}`}
|
||||
>
|
||||
{pageNum}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<Link
|
||||
href={getPageUrl(currentPage + 1)}
|
||||
className={`px-4 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition ${currentPage >= totalPages ? "opacity-40 cursor-not-allowed" : ""}`}
|
||||
>
|
||||
下一页
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* 底部 */}
|
||||
<div className="border-t border-gray-200 py-8 mt-16">
|
||||
<div className="max-w-6xl mx-auto px-6 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-7 h-7 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-xs">AI</span>
|
||||
</div>
|
||||
<span className="font-semibold text-gray-700 text-sm">江苏冲浪软件科技有限公司</span>
|
||||
</div>
|
||||
<div className="text-gray-400 text-sm text-center">
|
||||
© 2026 江苏冲浪软件科技有限公司 · AI 智能体广场
|
||||
</div>
|
||||
<div className="flex gap-4 text-xs text-gray-400">
|
||||
<a className="hover:text-gray-600 cursor-pointer transition">关于我们</a>
|
||||
<a className="hover:text-gray-600 cursor-pointer transition">隐私政策</a>
|
||||
<a className="hover:text-gray-600 cursor-pointer transition">联系我们</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user