更新功能展示

This commit is contained in:
537yaha
2026-03-30 13:46:29 +08:00
parent a28cab2a48
commit 03c23e0880
15 changed files with 230 additions and 107 deletions
+5 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
<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"
}`}
>
</TabsTrigger>
<TabsTrigger
value="visitor"
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="ai-config"
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="查看下一张"
>
AI配置
</TabsTrigger>
<TabsTrigger
value="users"
className="text-[15px] rounded-md px-3.5 py-2 transition-colors hover:bg-background/60"
>
</TabsTrigger>
<TabsTrigger
value="faq"
className="text-[15px] rounded-md px-3.5 py-2 transition-colors hover:bg-background/60"
>
FAQ管理
</TabsTrigger>
</TabsList>
<TabsContent value="dashboard" 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="dashboard.png"
placeholderIcon={LayoutDashboard}
placeholderText="工作台界面"
alt="AI-CS 工作台界面"
imageName={screenshotCards[prevScreenshotIndex].imageName}
placeholderIcon={screenshotCards[prevScreenshotIndex].placeholderIcon}
placeholderText={screenshotCards[prevScreenshotIndex].placeholderText}
alt={screenshotCards[prevScreenshotIndex].alt}
/>
</div>
</TabsContent>
<TabsContent value="visitor" 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="visitor.png"
placeholderIcon={Globe}
placeholderText="访客端界面"
alt="AI-CS 访客端界面"
imageName={screenshotCards[nextScreenshotIndex].imageName}
placeholderIcon={screenshotCards[nextScreenshotIndex].placeholderIcon}
placeholderText={screenshotCards[nextScreenshotIndex].placeholderText}
alt={screenshotCards[nextScreenshotIndex].alt}
/>
</div>
</TabsContent>
<TabsContent value="ai-config" 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="ai-config.png"
placeholderIcon={Bot}
placeholderText="AI配置界面"
alt="AI-CS AI配置界面"
imageName={screenshotCards[activeScreenshot].imageName}
placeholderIcon={screenshotCards[activeScreenshot].placeholderIcon}
placeholderText={screenshotCards[activeScreenshot].placeholderText}
alt={screenshotCards[activeScreenshot].alt}
/>
</div>
</TabsContent>
<TabsContent value="users" className="mt-0">
<div className="overflow-hidden rounded-xl border border-border/60 shadow-sm">
<ScreenshotDisplay
imageName="users.png"
placeholderIcon={Users}
placeholderText="用户管理界面"
alt="AI-CS 用户管理界面"
/>
</div>
</TabsContent>
<TabsContent value="faq" className="mt-0">
<div className="overflow-hidden rounded-xl border border-border/60 shadow-sm">
<ScreenshotDisplay
imageName="faq.png"
placeholderIcon={FileText}
placeholderText="FAQ管理界面"
alt="AI-CS FAQ管理界面"
/>
</div>
</TabsContent>
</Tabs>
</div>
</div>
</div>
</div>
</section>
+1 -3
View File
@@ -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 }>,
// 其他配置
Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 510 KiB