import { NextRequest, NextResponse } from "next/server"; import { readFileSync } from "fs"; import { resolve } from "path"; import { prisma } from "@/lib/prisma"; const BOT_DATA_PATH = resolve(process.cwd(), "data", "bot-characters.json"); const FORUM_NAMES: Record = { "ec-platform": "平台电商", "ec-livestream": "直播带货", "ec-private": "私域电商", "ec-group": "社区团购", "ec-dtc": "独立站/DTC", "ec-supply": "选品与供应链", "ai-tools-app": "AI工具与应用", "ai-startup-forum": "AI创业", "ai-saas": "SaaS出海", "ai-hardware": "智能硬件", "ai-llm": "大模型与开源", "ct-short-video": "短视频创作", "ct-live": "直播运营", "ct-writing": "图文内容", "ct-mcn": "MCN机构", "ct-podcast": "播客与音频", "fi-stock": "股票与基金", "fi-crypto": "加密货币/Web3", "fi-insurance": "保险与理财", "fi-pe": "PE与VC", "fi-forex": "外汇与期货", "cb-ecommerce": "跨境电商平台", "cb-factory": "外贸工厂/B2B", "cb-logistics": "海外仓与物流", "cb-payment": "跨境支付合规", "cb-brand": "品牌出海/DTC", "re-residential": "住宅买卖", "re-commercial": "商业地产", "re-renovation": "装修与家居", "re-property": "物业与租赁", "fd-restaurant": "餐饮开店", "fd-tea": "茶饮与咖啡", "fd-prepared": "预制菜加工", "fd-supply": "食品供应链", "fd-delivery": "外卖运营", "ll-beauty": "美容美发", "ll-fitness": "健身运动", "ll-pet": "宠物经济", "ll-housekeeping": "家政服务", "ll-repair": "维修与数码", "ll-edu-local": "本地教培", "hl-cosmetic": "医疗美容", "hl-wellness": "健康管理", "hl-elderly": "养老服务", "hl-rehab": "运动康复", "hl-mental": "心理健康", "edu-skills": "职业技能", "edu-knowledge": "知识付费", "edu-abroad": "留学与语言", "edu-corporate": "企业培训", "edu-k12": "K12与家庭", }; interface BotCharacter { key: string; displayName: string; email: string; role?: string; avatarPrompt: string; personality: { identity: string; expertise: string[]; weakness: string[]; stance: string; speakingStyle: string; catchphrase: string; forbiddenPatterns: string[]; wordLimit: { min: number; max: number }; passerbyType?: string; }; primaryForums: string[]; activeHours: { weekday: string[]; weekend: string[] }; activityLevel: string; replyChance: number; } function loadBotCharacters(): BotCharacter[] { try { const raw = readFileSync(BOT_DATA_PATH, "utf-8"); const data = JSON.parse(raw); return (data.characters || []) as BotCharacter[]; } catch { return []; } } export async function GET(req: NextRequest) { try { const { searchParams } = new URL(req.url); const role = searchParams.get("role"); const forumSlug = searchParams.get("forum"); const search = (searchParams.get("q") || "").toLowerCase().trim(); const characters = loadBotCharacters(); let filtered = characters; if (role === "passerby") filtered = filtered.filter(c => c.role === "passerby"); if (role === "expert") filtered = filtered.filter(c => c.role !== "passerby"); if (forumSlug) filtered = filtered.filter(c => (c.primaryForums || []).includes(forumSlug)); if (search) { filtered = filtered.filter(c => { const p = c.personality || {}; const text = [ c.displayName || "", c.key || "", p.identity || "", p.stance || "", (p.expertise || []).join(" "), ].join(" ").toLowerCase(); return text.includes(search); }); } const emails = filtered.map(c => `bot_${c.key}@zhuiguang.ai`); const userRows = emails.length > 0 ? await prisma.user.findMany({ where: { email: { in: emails } }, select: { id: true, name: true, email: true, avatarUrl: true, points: true, level: true, createdAt: true }, }) : []; const userByEmail = new Map(userRows.map(u => [u.email, u])); const userIds = userRows.map(u => u.id); const [topicGroups, postGroups, recentTopics] = await Promise.all([ userIds.length > 0 ? prisma.forumTopic.groupBy({ by: ["userId"], where: { userId: { in: userIds }, isHidden: false }, _count: { _all: true }, }) : Promise.resolve([] as Array<{ userId: number; _count: { _all: number } }>), userIds.length > 0 ? prisma.forumPost.groupBy({ by: ["userId"], where: { userId: { in: userIds }, isHidden: false }, _count: { _all: true }, }) : Promise.resolve([] as Array<{ userId: number; _count: { _all: number } }>), userIds.length > 0 ? prisma.forumTopic.findMany({ where: { userId: { in: userIds }, createdAt: { gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }, isHidden: false, }, select: { userId: true, createdAt: true, title: true, id: true, category: { select: { name: true, slug: true } }, }, orderBy: { createdAt: "desc" }, take: 200, }) : Promise.resolve([] as Array), ]); const topicMap = new Map(topicGroups.map(t => [t.userId, t._count._all])); const postMap = new Map(postGroups.map(p => [p.userId, p._count._all])); const recentMap = new Map(); for (const t of recentTopics) { const arr = recentMap.get(t.userId) || []; arr.push(t); recentMap.set(t.userId, arr); } const enriched = filtered.map(c => { const u = userByEmail.get(`bot_${c.key}@zhuiguang.ai`); const isPasserby = c.role === "passerby"; return { key: c.key, displayName: c.displayName, role: isPasserby ? "passerby" : "expert", avatarUrl: u?.avatarUrl || null, avatarPrompt: c.avatarPrompt, primaryForums: c.primaryForums || [], primaryForumNames: (c.primaryForums || []).map(s => FORUM_NAMES[s] || s), activityLevel: c.activityLevel, activeHours: c.activeHours, expertise: c.personality?.expertise || [], identity: c.personality?.identity || "", catchphrase: c.personality?.catchphrase || "", stance: c.personality?.stance || "", speakingStyle: c.personality?.speakingStyle || "", passerbyType: c.personality?.passerbyType || null, userId: u?.id || null, points: u?.points || 0, level: u?.level || null, createdAt: u?.createdAt || null, stats: { topics: u ? topicMap.get(u.id) || 0 : 0, posts: u ? postMap.get(u.id) || 0 : 0, }, recentTopics: u ? (recentMap.get(u.id) || []).slice(0, 3) : [], }; }); enriched.sort((a, b) => { const aa = a.stats.topics + a.stats.posts; const bb = b.stats.topics + b.stats.posts; if (bb !== aa) return bb - aa; return a.displayName.localeCompare(b.displayName); }); return NextResponse.json({ bots: enriched, total: enriched.length, forums: Object.entries(FORUM_NAMES).map(([slug, name]) => ({ slug, name })), }); } catch (error: unknown) { const err = error as { message?: string }; return NextResponse.json({ error: err.message || "获取Bot列表失败" }, { status: 500 }); } }