HomePageClient.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. "use client";
  2. import { useEffect, useState } from "react";
  3. import Link from "next/link";
  4. import { Button } from "@/components/ui/button";
  5. import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
  6. import {
  7. Bot,
  8. BookOpen,
  9. Users,
  10. Wand2,
  11. LineChart,
  12. ScrollText,
  13. Globe,
  14. LayoutDashboard,
  15. FileText,
  16. ArrowRight,
  17. ChevronLeft,
  18. ChevronRight,
  19. MessageSquare,
  20. Github,
  21. Mail,
  22. } from "lucide-react";
  23. import { ScreenshotDisplay } from "@/components/ScreenshotDisplay";
  24. import { ChatWidget } from "@/components/visitor/ChatWidget";
  25. import { FloatingButton } from "@/components/visitor/FloatingButton";
  26. import { Header } from "@/components/layout/Header";
  27. import { Footer } from "@/components/layout/Footer";
  28. import { FadeIn, FadeInStagger, FadeInItem } from "@/components/ui/fade-in";
  29. import { websiteConfig } from "@/lib/website-config";
  30. import { stats } from "@/lib/stats-config";
  31. const capabilityCards = [
  32. {
  33. icon: Bot,
  34. title: "多模型 AI 客服",
  35. description:
  36. "支持配置多家大模型与绘画等能力,访客与后台可统一管理模型与使用方式,便于替换供应商、控制成本。",
  37. },
  38. {
  39. icon: BookOpen,
  40. title: "知识库与 RAG",
  41. description:
  42. "文档入库、向量检索,让回答贴近你的业务资料;回复可标记是否使用知识库、模型或联网,便于核对与优化。",
  43. },
  44. {
  45. icon: Wand2,
  46. title: "提示词工程",
  47. description:
  48. "配置系统中使用的提示词模板,用于不同领域 RAG、联网等不同的业务场景。",
  49. },
  50. {
  51. icon: Users,
  52. title: "人工客服与实时协作",
  53. description:
  54. "在线状态、会话实时推送(WebSocket),支持人工接管与日常协作;访客小窗可嵌入任意站点。",
  55. },
  56. {
  57. icon: LineChart,
  58. title: "可视化报表",
  59. description:
  60. "按日或自定义区间查看访客小窗打开、会话与消息、AI 回复与失败率、知识库命中率等指标,快速掌握运营态势。",
  61. },
  62. {
  63. icon: ScrollText,
  64. title: "日志中心",
  65. description:
  66. "结构化日志按分类与事件落库,支持 trace_id 与关键字筛选,关键链路与异常可追溯,便于排障与审计。",
  67. },
  68. ];
  69. const steps = [
  70. {
  71. title: "克隆与配置",
  72. body: "复制 .env 模板,填好数据库与管理员等必填项。",
  73. },
  74. {
  75. title: "一键启动",
  76. body: "使用 Docker Compose 拉起前后端与依赖服务(详见 README)。",
  77. },
  78. {
  79. title: "嵌入访客端",
  80. body: "在站点中挂载聊天小窗,后台完成模型与知识库配置后即可对外服务。",
  81. },
  82. ];
  83. const screenshotCards = [
  84. {
  85. key: "dashboard",
  86. title: "工作台",
  87. imageName: "dashboard.png",
  88. placeholderIcon: LayoutDashboard,
  89. placeholderText: "工作台界面",
  90. alt: "AI-CS 工作台界面",
  91. },
  92. {
  93. key: "visitor",
  94. title: "访客端",
  95. imageName: "visitor.png",
  96. placeholderIcon: Globe,
  97. placeholderText: "访客端界面",
  98. alt: "AI-CS 访客端界面",
  99. },
  100. {
  101. key: "ai-config",
  102. title: "AI配置",
  103. imageName: "ai-config.png",
  104. placeholderIcon: Bot,
  105. placeholderText: "AI配置界面",
  106. alt: "AI-CS AI配置界面",
  107. },
  108. {
  109. key: "users",
  110. title: "用户管理",
  111. imageName: "users.png",
  112. placeholderIcon: Users,
  113. placeholderText: "用户管理界面",
  114. alt: "AI-CS 用户管理界面",
  115. },
  116. {
  117. key: "faq",
  118. title: "FAQ管理",
  119. imageName: "faq.png",
  120. placeholderIcon: FileText,
  121. placeholderText: "FAQ管理界面",
  122. alt: "AI-CS FAQ管理界面",
  123. },
  124. {
  125. key: "knowledge",
  126. title: "知识库管理",
  127. imageName: "knowledge.png",
  128. placeholderIcon: BookOpen,
  129. placeholderText: "知识库管理界面",
  130. alt: "AI-CS 知识库管理界面",
  131. },
  132. {
  133. key: "conversations",
  134. title: "知识库测试",
  135. imageName: "conversations.png",
  136. placeholderIcon: MessageSquare,
  137. placeholderText: "知识库测试界面",
  138. alt: "AI-CS 知识库测试界面",
  139. },
  140. {
  141. key: "prompts",
  142. title: "提示词工程",
  143. imageName: "prompts.png",
  144. placeholderIcon: Wand2,
  145. placeholderText: "提示词工程界面",
  146. alt: "AI-CS 提示词工程界面",
  147. },
  148. {
  149. key: "logs",
  150. title: "日志中心",
  151. imageName: "logs.png",
  152. placeholderIcon: ScrollText,
  153. placeholderText: "日志中心界面",
  154. alt: "AI-CS 日志中心界面",
  155. },
  156. {
  157. key: "analytics",
  158. title: "可视化报表",
  159. imageName: "analytics.png",
  160. placeholderIcon: LineChart,
  161. placeholderText: "可视化报表界面",
  162. alt: "AI-CS 可视化报表界面",
  163. },
  164. ];
  165. export function HomePageClient() {
  166. const [visitorId, setVisitorId] = useState<number | null>(null);
  167. const [isChatOpen, setIsChatOpen] = useState(false);
  168. const [activeScreenshot, setActiveScreenshot] = useState(0);
  169. useEffect(() => {
  170. let stored = window.localStorage.getItem("visitor_id");
  171. if (!stored) {
  172. stored = `${Date.now()}${Math.floor(Math.random() * 100000)}`;
  173. window.localStorage.setItem("visitor_id", stored);
  174. }
  175. const parsed = Number.parseInt(stored, 10);
  176. setVisitorId(Number.isNaN(parsed) ? null : parsed);
  177. }, []);
  178. const handleToggleChat = () => setIsChatOpen((prev) => !prev);
  179. const handleOpenChat = () => {
  180. if (visitorId === null) {
  181. setTimeout(() => setIsChatOpen(true), 500);
  182. } else {
  183. setIsChatOpen(true);
  184. }
  185. };
  186. const totalScreenshots = screenshotCards.length;
  187. const prevScreenshotIndex =
  188. (activeScreenshot - 1 + totalScreenshots) % totalScreenshots;
  189. const nextScreenshotIndex = (activeScreenshot + 1) % totalScreenshots;
  190. const goPrevScreenshot = () => {
  191. setActiveScreenshot((prev) => (prev - 1 + totalScreenshots) % totalScreenshots);
  192. };
  193. const goNextScreenshot = () => {
  194. setActiveScreenshot((prev) => (prev + 1) % totalScreenshots);
  195. };
  196. useEffect(() => {
  197. const timer = window.setInterval(() => {
  198. setActiveScreenshot((prev) => (prev + 1) % totalScreenshots);
  199. }, 4800);
  200. return () => window.clearInterval(timer);
  201. }, [totalScreenshots]);
  202. return (
  203. <div className="min-h-screen bg-background text-foreground">
  204. <Header />
  205. {/* Hero(回归旧版文案气质,保留新版三按钮) */}
  206. <section className="relative overflow-hidden border-b border-border/40">
  207. <div
  208. className="pointer-events-none absolute inset-0 bg-[radial-gradient(120%_80%_at_50%_-20%,rgba(37,99,235,0.14),transparent_55%)]"
  209. aria-hidden
  210. />
  211. <div
  212. className="pointer-events-none absolute inset-0 bg-gradient-to-b from-blue-50/80 via-background to-background"
  213. aria-hidden
  214. />
  215. <div className="container relative mx-auto px-6 pb-32 pt-20 md:pb-40 md:pt-28 lg:pt-28 xl:max-w-[1280px]">
  216. <FadeIn>
  217. <div className="mx-auto max-w-4xl text-center">
  218. <p className="mb-4 text-sm font-medium text-muted-foreground tracking-wide uppercase">
  219. AI 智能客服
  220. </p>
  221. <h1 className="mb-6 text-balance text-4xl font-bold tracking-tight text-foreground sm:text-5xl md:text-6xl md:leading-[1.12]">
  222. 让客户服务更简单、更高效
  223. </h1>
  224. <p className="mx-auto mb-10 max-w-3xl text-pretty text-lg sm:text-xl text-muted-foreground leading-relaxed">
  225. 7×24 小时智能应答,AI 与人工无缝切换,释放团队时间专注更有价值的事
  226. </p>
  227. <div className="flex flex-col items-stretch justify-center gap-3 sm:flex-row sm:flex-wrap sm:items-center">
  228. <Button
  229. size="lg"
  230. className="rounded-xl bg-blue-600 px-8 py-6 text-[15px] shadow-sm transition-all hover:bg-blue-500 hover:shadow-md"
  231. onClick={handleOpenChat}
  232. >
  233. 立即体验
  234. <ArrowRight className="ml-2 h-4 w-4" />
  235. </Button>
  236. <Button
  237. size="lg"
  238. variant="outline"
  239. className="rounded-xl border-border/80 px-8 py-6 text-[15px] bg-background/60 backdrop-blur-sm"
  240. asChild
  241. >
  242. <Link
  243. href="/agent/login"
  244. target="_blank"
  245. rel="noopener noreferrer"
  246. className="inline-flex items-center justify-center gap-2"
  247. >
  248. 客服登录
  249. </Link>
  250. </Button>
  251. </div>
  252. <p className="mt-4 text-sm text-muted-foreground">无需等待,可立即使用</p>
  253. </div>
  254. </FadeIn>
  255. </div>
  256. </section>
  257. {/* 数字条(沿用旧版) */}
  258. <section className="py-16 md:py-20 border-t border-border/50">
  259. <FadeIn>
  260. <div className="container mx-auto px-6">
  261. <p className="text-xs font-medium text-muted-foreground text-center mb-8 tracking-wide">
  262. 深受企业信赖
  263. </p>
  264. <div className="grid grid-cols-2 md:grid-cols-4 gap-10 max-w-6xl mx-auto">
  265. {stats.map((stat) => (
  266. <div key={stat.label} className="text-center">
  267. <div className="text-3xl md:text-4xl font-semibold text-foreground">{stat.value}</div>
  268. <div className="text-sm text-muted-foreground mt-1">{stat.label}</div>
  269. </div>
  270. ))}
  271. </div>
  272. </div>
  273. </FadeIn>
  274. </section>
  275. {/* 核心能力 */}
  276. <section id="features" className="relative scroll-mt-20">
  277. <div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-blue-200/50 to-transparent" aria-hidden />
  278. <div className="container mx-auto px-6 py-20 md:py-28">
  279. <FadeIn>
  280. <div className="mb-14 text-center px-4">
  281. <h2 className="mb-3 text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
  282. 核心能力
  283. </h2>
  284. <p className="mx-auto max-w-xl text-base text-muted-foreground">
  285. 从模型、知识库、提示词到人工协作、报表与日志,一套系统串起来。
  286. </p>
  287. </div>
  288. </FadeIn>
  289. <FadeInStagger className="mx-auto grid max-w-6xl grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 lg:gap-6">
  290. {capabilityCards.map((item) => {
  291. const Icon = item.icon;
  292. return (
  293. <FadeInItem key={item.title}>
  294. <Card className="group h-full border border-border/60 bg-card/90 shadow-sm backdrop-blur-sm transition-all duration-300 hover:-translate-y-0.5 hover:border-blue-200/70 hover:shadow-md">
  295. <CardHeader className="pb-3">
  296. <div className="mb-4 flex h-11 w-11 items-center justify-center rounded-xl border border-blue-100/80 bg-gradient-to-br from-blue-50 to-background text-blue-700 transition-transform duration-300 group-hover:scale-[1.03]">
  297. <Icon className="h-5 w-5" />
  298. </div>
  299. <CardTitle className="text-lg font-semibold tracking-tight">
  300. {item.title}
  301. </CardTitle>
  302. <CardDescription className="text-sm leading-relaxed text-muted-foreground">
  303. {item.description}
  304. </CardDescription>
  305. </CardHeader>
  306. </Card>
  307. </FadeInItem>
  308. );
  309. })}
  310. </FadeInStagger>
  311. </div>
  312. </section>
  313. {/* 界面展示 */}
  314. <FadeIn>
  315. <section
  316. id="screenshots"
  317. className="scroll-mt-20 border-t border-border/40 bg-muted/20 py-20 md:py-28"
  318. >
  319. <div className="container mx-auto px-6">
  320. <div className="mb-14 text-center px-4">
  321. <h2 className="mb-3 text-3xl font-semibold tracking-tight sm:text-4xl">
  322. 界面展示
  323. </h2>
  324. <p className="mx-auto max-w-xl text-muted-foreground">
  325. 精心设计的界面,让管理更轻松
  326. </p>
  327. </div>
  328. <div className="mx-auto max-w-6xl">
  329. <div className="mb-8 flex flex-wrap justify-center gap-2">
  330. {screenshotCards.map((item, idx) => (
  331. <button
  332. key={item.key}
  333. type="button"
  334. onClick={() => setActiveScreenshot(idx)}
  335. className={`rounded-full px-4 py-1.5 text-sm transition-all ${
  336. idx === activeScreenshot
  337. ? "bg-blue-600 text-white shadow-sm"
  338. : "bg-background text-muted-foreground border border-border/70 hover:text-foreground hover:border-blue-200"
  339. }`}
  340. >
  341. {item.title}
  342. </button>
  343. ))}
  344. </div>
  345. <div className="relative mx-auto max-w-6xl px-4 md:px-8">
  346. <div className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(80%_60%_at_50%_40%,rgba(37,99,235,0.14),transparent_70%)]" />
  347. <div className="relative h-[290px] md:h-[420px] lg:h-[510px]">
  348. <button
  349. type="button"
  350. onClick={goPrevScreenshot}
  351. className="absolute left-0 top-1/2 z-40 -translate-y-1/2 rounded-full border border-border/70 bg-background/90 p-2.5 shadow-sm backdrop-blur transition hover:border-blue-200 hover:bg-background"
  352. aria-label="查看上一张"
  353. >
  354. <ChevronLeft className="h-5 w-5" />
  355. </button>
  356. <button
  357. type="button"
  358. onClick={goNextScreenshot}
  359. className="absolute right-0 top-1/2 z-40 -translate-y-1/2 rounded-full border border-border/70 bg-background/90 p-2.5 shadow-sm backdrop-blur transition hover:border-blue-200 hover:bg-background"
  360. aria-label="查看下一张"
  361. >
  362. <ChevronRight className="h-5 w-5" />
  363. </button>
  364. {/* 左侧半露卡片 */}
  365. <div className="absolute left-6 right-[42%] top-7 z-10 hidden overflow-hidden rounded-2xl border border-border/60 bg-background/85 shadow-md md:block">
  366. <div className="pointer-events-none absolute inset-0 z-10 bg-background/18" />
  367. <ScreenshotDisplay
  368. imageName={screenshotCards[prevScreenshotIndex].imageName}
  369. placeholderIcon={screenshotCards[prevScreenshotIndex].placeholderIcon}
  370. placeholderText={screenshotCards[prevScreenshotIndex].placeholderText}
  371. alt={screenshotCards[prevScreenshotIndex].alt}
  372. />
  373. </div>
  374. {/* 右侧半露卡片 */}
  375. <div className="absolute left-[42%] right-6 top-7 z-10 hidden overflow-hidden rounded-2xl border border-border/60 bg-background/85 shadow-md md:block">
  376. <div className="pointer-events-none absolute inset-0 z-10 bg-background/18" />
  377. <ScreenshotDisplay
  378. imageName={screenshotCards[nextScreenshotIndex].imageName}
  379. placeholderIcon={screenshotCards[nextScreenshotIndex].placeholderIcon}
  380. placeholderText={screenshotCards[nextScreenshotIndex].placeholderText}
  381. alt={screenshotCards[nextScreenshotIndex].alt}
  382. />
  383. </div>
  384. {/* 中间主卡片 */}
  385. <div className="absolute inset-x-8 top-0 z-30 overflow-hidden rounded-2xl border border-border/70 bg-background shadow-xl ring-1 ring-blue-100/60 md:inset-x-16 lg:inset-x-24">
  386. <ScreenshotDisplay
  387. imageName={screenshotCards[activeScreenshot].imageName}
  388. placeholderIcon={screenshotCards[activeScreenshot].placeholderIcon}
  389. placeholderText={screenshotCards[activeScreenshot].placeholderText}
  390. alt={screenshotCards[activeScreenshot].alt}
  391. />
  392. </div>
  393. </div>
  394. </div>
  395. </div>
  396. </div>
  397. </section>
  398. </FadeIn>
  399. {/* 快速接入 */}
  400. <section id="quick-start" className="relative scroll-mt-20 border-t border-border/40">
  401. <div
  402. className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-border to-transparent"
  403. aria-hidden
  404. />
  405. <div className="container mx-auto px-6 py-20 md:py-28">
  406. <FadeIn>
  407. <div className="mb-12 text-center px-4">
  408. <h2 className="mb-3 text-3xl font-semibold tracking-tight sm:text-4xl">
  409. 快速接入
  410. </h2>
  411. <p className="text-muted-foreground">三步跑通,从仓库到访客小窗。</p>
  412. </div>
  413. </FadeIn>
  414. <FadeInStagger className="mx-auto grid max-w-4xl grid-cols-1 gap-8 md:grid-cols-3">
  415. {steps.map((step, i) => (
  416. <FadeInItem key={step.title}>
  417. <div className="relative rounded-2xl border border-border/60 bg-card/50 p-6 text-center md:text-left transition-all duration-300 hover:border-blue-200/70 hover:shadow-md hover:-translate-y-0.5">
  418. <div className="mx-auto mb-4 flex h-10 w-10 items-center justify-center rounded-full border border-blue-200/80 bg-blue-50/80 text-sm font-semibold text-blue-800 md:mx-0">
  419. {i + 1}
  420. </div>
  421. <h3 className="mb-2 font-semibold">{step.title}</h3>
  422. <p className="text-sm leading-relaxed text-muted-foreground">{step.body}</p>
  423. </div>
  424. </FadeInItem>
  425. ))}
  426. </FadeInStagger>
  427. </div>
  428. </section>
  429. {/* 收尾 CTA */}
  430. <section className="relative border-t border-border/40 overflow-hidden">
  431. <div
  432. className="pointer-events-none absolute inset-0 bg-gradient-to-br from-blue-600/[0.07] via-transparent to-blue-400/[0.06]"
  433. aria-hidden
  434. />
  435. <div className="container relative mx-auto px-6 py-20 text-center md:py-28">
  436. <FadeIn>
  437. <h2 className="mb-4 text-3xl font-semibold tracking-tight sm:text-4xl">
  438. 准备好把 AI-CS 接到你的产品里了吗?
  439. </h2>
  440. <p className="mx-auto mb-10 max-w-lg text-muted-foreground leading-relaxed">
  441. 从开源仓库开始,或用在线 Demo 先看交互与能力边界。
  442. </p>
  443. <div className="flex flex-col items-stretch justify-center gap-3 sm:flex-row sm:flex-wrap sm:justify-center">
  444. <Button size="lg" className="rounded-xl bg-blue-600 px-8 shadow-sm hover:bg-blue-500" asChild>
  445. <a
  446. href={websiteConfig.github.repo}
  447. target="_blank"
  448. rel="noopener noreferrer"
  449. className="inline-flex items-center justify-center gap-2"
  450. >
  451. <Github className="h-4 w-4" />
  452. Star / Fork 仓库
  453. </a>
  454. </Button>
  455. <Button size="lg" variant="outline" className="rounded-xl border-border/80 px-8 bg-background/80" asChild>
  456. <a
  457. // 点击后直接唤起邮箱客户端(可在客户端自动带上主题/正文)
  458. href={`mailto:2930134478@qq.com?subject=${encodeURIComponent("AI-CS 建议反馈")}&body=${encodeURIComponent(
  459. "你好,我想反馈:\n\n1)问题/建议:\n2)影响范围/环境:\n3)期望结果:\n\n---\n联系方式(可选):"
  460. )}`}
  461. className="inline-flex items-center justify-center gap-2"
  462. >
  463. 建议反馈
  464. <Mail className="h-3.5 w-3.5" />
  465. </a>
  466. </Button>
  467. </div>
  468. </FadeIn>
  469. </div>
  470. </section>
  471. <Footer />
  472. {visitorId !== null && (
  473. <>
  474. <FloatingButton onClick={handleToggleChat} isOpen={isChatOpen} />
  475. {isChatOpen && (
  476. <ChatWidget visitorId={visitorId} isOpen={isChatOpen} onToggle={handleToggleChat} />
  477. )}
  478. </>
  479. )}
  480. </div>
  481. );
  482. }