537yaha пре 3 месеци
родитељ
комит
03c23e0880

+ 5 - 1
README.md

@@ -187,6 +187,10 @@ npm run dev
 </script>
 ```
 
+## 相关文档
+
+- **知识库 / 内部 Wiki 导入(项目总览一篇通)**:[doc/AI-CS-知识库-项目总览.md](doc/AI-CS-知识库-项目总览.md)
+
 ## 常见问题与排障(先看这里)
 
 - **提示音听不到**:浏览器通常需要“用户一次交互”才能解锁音频;请先点一下页面任意按钮/再打开喇叭开关测试
@@ -203,4 +207,4 @@ npm run dev
 
 ---
 
-**最后更新**:2026-03-25
+**最后更新**:2026-03-27

+ 55 - 29
frontend/app/agent/knowledge/page.tsx

@@ -125,35 +125,45 @@ export default function KnowledgePage(props: any = {}) {
     }
   }, []);
 
-  // 加载文档列表
-  const loadDocuments = useCallback(async () => {
-    if (!selectedKnowledgeBase) {
-      setDocuments([]);
-      setDocumentResult(null);
-      return;
-    }
+  // 加载文档列表(silent:后台轮询向量化状态时不全屏“加载中”、不弹 Toast,避免刷屏)
+  const loadDocuments = useCallback(
+    async (opts?: { silent?: boolean }) => {
+      if (!selectedKnowledgeBase) {
+        setDocuments([]);
+        setDocumentResult(null);
+        return;
+      }
 
-    setLoadingDocs(true);
-    try {
-      const status = statusFilter === "all" ? undefined : statusFilter;
-      const result = await fetchDocuments(
-        selectedKnowledgeBase.id,
-        currentPage,
-        pageSize,
-        searchKeyword || undefined,
-        status
-      );
-      setDocumentResult(result);
-      setDocuments(result.documents ?? []);
-    } catch (error) {
-      console.error("加载文档列表失败:", error);
-      toast.error((error as Error).message || "加载文档列表失败");
-      setDocuments([]);
-      setDocumentResult(null);
-    } finally {
-      setLoadingDocs(false);
-    }
-  }, [selectedKnowledgeBase, currentPage, searchKeyword, statusFilter]);
+      const silent = opts?.silent === true;
+      if (!silent) {
+        setLoadingDocs(true);
+      }
+      try {
+        const status = statusFilter === "all" ? undefined : statusFilter;
+        const result = await fetchDocuments(
+          selectedKnowledgeBase.id,
+          currentPage,
+          pageSize,
+          searchKeyword || undefined,
+          status
+        );
+        setDocumentResult(result);
+        setDocuments(result.documents ?? []);
+      } catch (error) {
+        console.error("加载文档列表失败:", error);
+        if (!silent) {
+          toast.error((error as Error).message || "加载文档列表失败");
+          setDocuments([]);
+          setDocumentResult(null);
+        }
+      } finally {
+        if (!silent) {
+          setLoadingDocs(false);
+        }
+      }
+    },
+    [selectedKnowledgeBase, currentPage, searchKeyword, statusFilter]
+  );
 
   // 初始加载
   useEffect(() => {
@@ -163,9 +173,25 @@ export default function KnowledgePage(props: any = {}) {
   // 当选择知识库或搜索条件变化时,重新加载文档
   useEffect(() => {
     setCurrentPage(1); // 切换知识库或搜索时重置页码
-    loadDocuments();
+    void loadDocuments();
   }, [loadDocuments]);
 
+  // 向量化进行中时自动刷新列表(pending → processing → completed/failed),无需手动整页刷新
+  useEffect(() => {
+    if (!selectedKnowledgeBase) return;
+    const hasEmbeddingInFlight = documents.some(
+      (d) => d.embedding_status === "pending" || d.embedding_status === "processing"
+    );
+    if (!hasEmbeddingInFlight) return;
+
+    const intervalMs = 2500;
+    const id = window.setInterval(() => {
+      void loadDocuments({ silent: true });
+    }, intervalMs);
+
+    return () => window.clearInterval(id);
+  }, [selectedKnowledgeBase, documents, loadDocuments]);
+
   // 选择知识库
   const handleSelectKnowledgeBase = (kb: KnowledgeBase) => {
     setSelectedKnowledgeBase(kb);

+ 2 - 1
frontend/components/ScreenshotDisplay.tsx

@@ -48,7 +48,8 @@ export function ScreenshotDisplay({
         onLoad={() => setImageLoaded(true)}
         sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
         priority={false}
-        unoptimized={false}
+        // 营销截图常替换;走 /_next/image 会强缓存优化结果,本地改 public 后仍像旧图
+        unoptimized
       />
       {!imageLoaded && !imageError && (
         <div className="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-primary/10 to-primary/5">

+ 167 - 73
frontend/components/marketing/HomePageClient.tsx

@@ -15,10 +15,12 @@ import {
   LayoutDashboard,
   FileText,
   ArrowRight,
+  ChevronLeft,
+  ChevronRight,
+  MessageSquare,
   Github,
   Mail,
 } from "lucide-react";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
 import { ScreenshotDisplay } from "@/components/ScreenshotDisplay";
 import { ChatWidget } from "@/components/visitor/ChatWidget";
 import { FloatingButton } from "@/components/visitor/FloatingButton";
@@ -82,9 +84,93 @@ const steps = [
   },
 ];
 
+const screenshotCards = [
+  {
+    key: "dashboard",
+    title: "工作台",
+    imageName: "dashboard.png",
+    placeholderIcon: LayoutDashboard,
+    placeholderText: "工作台界面",
+    alt: "AI-CS 工作台界面",
+  },
+  {
+    key: "visitor",
+    title: "访客端",
+    imageName: "visitor.png",
+    placeholderIcon: Globe,
+    placeholderText: "访客端界面",
+    alt: "AI-CS 访客端界面",
+  },
+  {
+    key: "ai-config",
+    title: "AI配置",
+    imageName: "ai-config.png",
+    placeholderIcon: Bot,
+    placeholderText: "AI配置界面",
+    alt: "AI-CS AI配置界面",
+  },
+  {
+    key: "users",
+    title: "用户管理",
+    imageName: "users.png",
+    placeholderIcon: Users,
+    placeholderText: "用户管理界面",
+    alt: "AI-CS 用户管理界面",
+  },
+  {
+    key: "faq",
+    title: "FAQ管理",
+    imageName: "faq.png",
+    placeholderIcon: FileText,
+    placeholderText: "FAQ管理界面",
+    alt: "AI-CS FAQ管理界面",
+  },
+  {
+    key: "knowledge",
+    title: "知识库管理",
+    imageName: "knowledge.png",
+    placeholderIcon: BookOpen,
+    placeholderText: "知识库管理界面",
+    alt: "AI-CS 知识库管理界面",
+  },
+  {
+    key: "conversations",
+    title: "知识库测试",
+    imageName: "conversations.png",
+    placeholderIcon: MessageSquare,
+    placeholderText: "知识库测试界面",
+    alt: "AI-CS 知识库测试界面",
+  },
+  {
+    key: "prompts",
+    title: "提示词工程",
+    imageName: "prompts.png",
+    placeholderIcon: Wand2,
+    placeholderText: "提示词工程界面",
+    alt: "AI-CS 提示词工程界面",
+  },
+  {
+    key: "logs",
+    title: "日志中心",
+    imageName: "logs.png",
+    placeholderIcon: ScrollText,
+    placeholderText: "日志中心界面",
+    alt: "AI-CS 日志中心界面",
+  },
+  {
+    key: "analytics",
+    title: "可视化报表",
+    imageName: "analytics.png",
+    placeholderIcon: LineChart,
+    placeholderText: "可视化报表界面",
+    alt: "AI-CS 可视化报表界面",
+  },
+];
+
 export function HomePageClient() {
   const [visitorId, setVisitorId] = useState<number | null>(null);
   const [isChatOpen, setIsChatOpen] = useState(false);
+  const [activeScreenshot, setActiveScreenshot] = useState(0);
 
   useEffect(() => {
     let stored = window.localStorage.getItem("visitor_id");
@@ -106,6 +192,26 @@ export function HomePageClient() {
     }
   };
 
+  const totalScreenshots = screenshotCards.length;
+  const prevScreenshotIndex =
+    (activeScreenshot - 1 + totalScreenshots) % totalScreenshots;
+  const nextScreenshotIndex = (activeScreenshot + 1) % totalScreenshots;
+
+  const goPrevScreenshot = () => {
+    setActiveScreenshot((prev) => (prev - 1 + totalScreenshots) % totalScreenshots);
+  };
+
+  const goNextScreenshot = () => {
+    setActiveScreenshot((prev) => (prev + 1) % totalScreenshots);
+  };
+
+  useEffect(() => {
+    const timer = window.setInterval(() => {
+      setActiveScreenshot((prev) => (prev + 1) % totalScreenshots);
+    }, 4800);
+    return () => window.clearInterval(timer);
+  }, [totalScreenshots]);
+
   return (
     <div className="min-h-screen bg-background text-foreground">
       <Header />
@@ -232,90 +338,78 @@ export function HomePageClient() {
               </p>
             </div>
             <div className="mx-auto max-w-6xl">
-              <Tabs defaultValue="dashboard" className="w-full">
-                <TabsList className="mb-8 grid w-full grid-cols-3 md:grid-cols-5 rounded-xl bg-muted/50 p-1.5">
-                  <TabsTrigger
-                    value="dashboard"
-                    className="text-[15px] rounded-md px-3.5 py-2 transition-colors hover:bg-background/60"
-                  >
-                    工作台
-                  </TabsTrigger>
-                  <TabsTrigger
-                    value="visitor"
-                    className="text-[15px] rounded-md px-3.5 py-2 transition-colors hover:bg-background/60"
-                  >
-                    访客端
-                  </TabsTrigger>
-                  <TabsTrigger
-                    value="ai-config"
-                    className="text-[15px] rounded-md px-3.5 py-2 transition-colors hover:bg-background/60"
+              <div className="mb-8 flex flex-wrap justify-center gap-2">
+                {screenshotCards.map((item, idx) => (
+                  <button
+                    key={item.key}
+                    type="button"
+                    onClick={() => setActiveScreenshot(idx)}
+                    className={`rounded-full px-4 py-1.5 text-sm transition-all ${
+                      idx === activeScreenshot
+                        ? "bg-blue-600 text-white shadow-sm"
+                        : "bg-background text-muted-foreground border border-border/70 hover:text-foreground hover:border-blue-200"
+                    }`}
                   >
-                    AI配置
-                  </TabsTrigger>
-                  <TabsTrigger
-                    value="users"
-                    className="text-[15px] rounded-md px-3.5 py-2 transition-colors hover:bg-background/60"
+                    {item.title}
+                  </button>
+                ))}
+              </div>
+
+              <div className="relative mx-auto max-w-6xl px-4 md:px-8">
+                <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%)]" />
+
+                <div className="relative h-[290px] md:h-[420px] lg:h-[510px]">
+                  <button
+                    type="button"
+                    onClick={goPrevScreenshot}
+                    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"
+                    aria-label="查看上一张"
                   >
-                    用户管理
-                  </TabsTrigger>
-                  <TabsTrigger
-                    value="faq"
-                    className="text-[15px] rounded-md px-3.5 py-2 transition-colors hover:bg-background/60"
+                    <ChevronLeft className="h-5 w-5" />
+                  </button>
+
+                  <button
+                    type="button"
+                    onClick={goNextScreenshot}
+                    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"
+                    aria-label="查看下一张"
                   >
-                    FAQ管理
-                  </TabsTrigger>
-                </TabsList>
-                <TabsContent value="dashboard" className="mt-0">
-                  <div className="overflow-hidden rounded-xl border border-border/60 shadow-sm">
-                    <ScreenshotDisplay
-                      imageName="dashboard.png"
-                      placeholderIcon={LayoutDashboard}
-                      placeholderText="工作台界面"
-                      alt="AI-CS 工作台界面"
-                    />
-                  </div>
-                </TabsContent>
-                <TabsContent value="visitor" className="mt-0">
-                  <div className="overflow-hidden rounded-xl border border-border/60 shadow-sm">
-                    <ScreenshotDisplay
-                      imageName="visitor.png"
-                      placeholderIcon={Globe}
-                      placeholderText="访客端界面"
-                      alt="AI-CS 访客端界面"
-                    />
-                  </div>
-                </TabsContent>
-                <TabsContent value="ai-config" className="mt-0">
-                  <div className="overflow-hidden rounded-xl border border-border/60 shadow-sm">
+                    <ChevronRight className="h-5 w-5" />
+                  </button>
+
+                  {/* 左侧半露卡片 */}
+                  <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">
+                    <div className="pointer-events-none absolute inset-0 z-10 bg-background/18" />
                     <ScreenshotDisplay
-                      imageName="ai-config.png"
-                      placeholderIcon={Bot}
-                      placeholderText="AI配置界面"
-                      alt="AI-CS AI配置界面"
+                      imageName={screenshotCards[prevScreenshotIndex].imageName}
+                      placeholderIcon={screenshotCards[prevScreenshotIndex].placeholderIcon}
+                      placeholderText={screenshotCards[prevScreenshotIndex].placeholderText}
+                      alt={screenshotCards[prevScreenshotIndex].alt}
                     />
                   </div>
-                </TabsContent>
-                <TabsContent value="users" className="mt-0">
-                  <div className="overflow-hidden rounded-xl border border-border/60 shadow-sm">
+
+                  {/* 右侧半露卡片 */}
+                  <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">
+                    <div className="pointer-events-none absolute inset-0 z-10 bg-background/18" />
                     <ScreenshotDisplay
-                      imageName="users.png"
-                      placeholderIcon={Users}
-                      placeholderText="用户管理界面"
-                      alt="AI-CS 用户管理界面"
+                      imageName={screenshotCards[nextScreenshotIndex].imageName}
+                      placeholderIcon={screenshotCards[nextScreenshotIndex].placeholderIcon}
+                      placeholderText={screenshotCards[nextScreenshotIndex].placeholderText}
+                      alt={screenshotCards[nextScreenshotIndex].alt}
                     />
                   </div>
-                </TabsContent>
-                <TabsContent value="faq" className="mt-0">
-                  <div className="overflow-hidden rounded-xl border border-border/60 shadow-sm">
+
+                  {/* 中间主卡片 */}
+                  <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">
                     <ScreenshotDisplay
-                      imageName="faq.png"
-                      placeholderIcon={FileText}
-                      placeholderText="FAQ管理界面"
-                      alt="AI-CS FAQ管理界面"
+                      imageName={screenshotCards[activeScreenshot].imageName}
+                      placeholderIcon={screenshotCards[activeScreenshot].placeholderIcon}
+                      placeholderText={screenshotCards[activeScreenshot].placeholderText}
+                      alt={screenshotCards[activeScreenshot].alt}
                     />
                   </div>
-                </TabsContent>
-              </Tabs>
+                </div>
+              </div>
             </div>
           </div>
         </section>

+ 1 - 3
frontend/lib/website-config.ts

@@ -24,9 +24,7 @@ export const websiteConfig = {
   // 友情链接(用于互相引流)
   // 格式:{ name: "链接名称", url: "链接地址" }
   friendLinks: [
-    // 示例:添加您的友情链接
-    // { name: "合作伙伴1", url: "https://example.com" },
-    // { name: "合作伙伴2", url: "https://example2.com" },
+    { name: "Poixe免费API赞助", url: "https://poixe.com/products/free" },
   ] as Array<{ name: string; url: string }>,
   
   // 其他配置

BIN
frontend/public/images/screenshots/ai-config.png


BIN
frontend/public/images/screenshots/analytics.png


BIN
frontend/public/images/screenshots/conversations.png


BIN
frontend/public/images/screenshots/dashboard.png


BIN
frontend/public/images/screenshots/faq.png


BIN
frontend/public/images/screenshots/knowledge.png


BIN
frontend/public/images/screenshots/logs.png


BIN
frontend/public/images/screenshots/prompts.png


BIN
frontend/public/images/screenshots/users.png


BIN
frontend/public/images/screenshots/visitor.png