Update application code and dependencies

This commit is contained in:
root
2026-05-06 17:22:50 +08:00
parent efc8f4bf78
commit a3ee04379d
60 changed files with 6793 additions and 860 deletions
+329
View File
@@ -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>
)
}