"use client";
import { useState, useCallback } from "react";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
BookOpen,
Plus,
Trash2,
Search,
FileText,
Link,
Type,
AlertCircle,
Loader2,
} from "lucide-react";
import {
knowledgeApi,
type KnowledgeBase,
type KnowledgeDocument,
} from "@/lib/api";
import { useApi } from "@/lib/hooks/use-api";
import { LoadingState, ErrorState } from "@/components/ui/api-states";
// ── 状态 Badge ─────────────────────────────────────────────────────────────────
function StatusBadge({ status }: { status: string }) {
switch (status) {
case "processing":
return 处理中;
case "ready":
return 就绪;
case "failed":
return 失败;
case "active":
return 活跃;
default:
return {status};
}
}
function SourceTypeBadge({ type }: { type: string }) {
switch (type) {
case "text":
return (
文本
);
case "url":
return (
URL
);
case "markdown":
return (
Markdown
);
default:
return {type};
}
}
// ── 空状态组件 ─────────────────────────────────────────────────────────────────
function EmptyState({ onCreateClick }: { onCreateClick: () => void }) {
return (
还没有知识库
创建您的第一个知识库,为AI内容生产提供精准的知识支撑
);
}
// ── 知识库卡片 + 展开文档列表 ────────────────────────────────────────────────────
function KnowledgeBaseCard({
kb,
isExpanded,
onToggle,
onDelete,
onUpload,
}: {
kb: KnowledgeBase;
isExpanded: boolean;
onToggle: () => void;
onDelete: (id: string) => void;
onUpload: (id: string) => void;
}) {
const docsUrl = isExpanded ? `/api/v1/knowledge/bases/${kb.id}/documents` : null;
const { data: documents = [], isLoading: docsLoading, error: docsApiError, refresh: refreshDocs } =
useApi(docsUrl);
const docsError = docsApiError?.message ?? null;
const handleDeleteDoc = async (docId: string) => {
try {
await knowledgeApi.deleteDocument(undefined, kb.id, docId);
refreshDocs();
} catch (err) {
console.error("Delete doc error:", err);
}
};
return (
{kb.name}
{kb.description && (
{kb.description}
)}
{kb.document_count} 文档
{isExpanded && (
文档列表
{docsLoading ? (
加载中...
) : docsError ? (
) : documents.length === 0 ? (
暂无文档,点击上方按钮上传
) : (
标题
来源
状态
分块数
操作
{documents.map((doc) => (
{doc.title}
{doc.chunk_count}
))}
)}
)}
);
}
// ── 主页面 ─────────────────────────────────────────────────────────────────────
export default function KnowledgePage() {
const [activeTab, setActiveTab] = useState("enterprise");
const [searchQuery, setSearchQuery] = useState("");
const [expandedKbId, setExpandedKbId] = useState(null);
// SWR data fetching
const {
data: enterpriseBases = [],
isLoading: enterpriseLoading,
error: enterpriseError,
mutate: mutateEnterprise,
} = useApi("/api/v1/knowledge/bases/?type=enterprise");
const {
data: industryBases = [],
isLoading: industryLoading,
error: industryError,
} = useApi("/api/v1/knowledge/bases/?type=industry");
const loading = enterpriseLoading || industryLoading;
const error = enterpriseError?.message || industryError?.message || null;
// Create KB dialog
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [newKbName, setNewKbName] = useState("");
const [newKbDescription, setNewKbDescription] = useState("");
const [createLoading, setCreateLoading] = useState(false);
const [createError, setCreateError] = useState(null);
// Upload doc dialog
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
const [uploadKbId, setUploadKbId] = useState(null);
const [docTitle, setDocTitle] = useState("");
const [docSourceType, setDocSourceType] = useState<"text" | "url" | "markdown">("text");
const [docContent, setDocContent] = useState("");
const [docUrl, setDocUrl] = useState("");
const [uploadLoading, setUploadLoading] = useState(false);
const [uploadError, setUploadError] = useState(null);
const fetchBases = useCallback(() => {
mutateEnterprise();
}, [mutateEnterprise]);
const currentBases = activeTab === "enterprise" ? enterpriseBases : industryBases;
const filteredBases = searchQuery
? currentBases.filter(
(kb) =>
kb.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
kb.description?.toLowerCase().includes(searchQuery.toLowerCase())
)
: currentBases;
const handleCreateKb = async () => {
if (!newKbName.trim()) return;
try {
setCreateLoading(true);
setCreateError(null);
await knowledgeApi.createBase(undefined, {
name: newKbName.trim(),
type: "enterprise",
description: newKbDescription.trim() || undefined,
});
mutateEnterprise();
setCreateDialogOpen(false);
setNewKbName("");
setNewKbDescription("");
} catch (err) {
setCreateError(err instanceof Error ? err.message : "创建失败");
} finally {
setCreateLoading(false);
}
};
const handleDeleteKb = async (kbId: string) => {
try {
await knowledgeApi.deleteBase(undefined, kbId);
mutateEnterprise();
if (expandedKbId === kbId) setExpandedKbId(null);
} catch (err) {
console.error("Delete KB error:", err);
}
};
const handleUploadDoc = async () => {
if (!uploadKbId || !docTitle.trim()) return;
try {
setUploadLoading(true);
setUploadError(null);
await knowledgeApi.uploadDocument(undefined, uploadKbId, {
title: docTitle.trim(),
source_type: docSourceType,
content: docSourceType !== "url" ? docContent : undefined,
source_url: docSourceType === "url" ? docUrl : undefined,
});
mutateEnterprise();
setUploadDialogOpen(false);
setDocTitle("");
setDocContent("");
setDocUrl("");
setDocSourceType("text");
} catch (err) {
setUploadError(err instanceof Error ? err.message : "上传失败");
} finally {
setUploadLoading(false);
}
};
const openUploadDialog = (kbId: string) => {
setUploadKbId(kbId);
setUploadError(null);
setUploadDialogOpen(true);
};
if (loading) {
return (
知识库
管理行业和企业知识,为AI内容生产提供智能支撑
);
}
return (
{/* 页面标题 */}
知识库
管理行业和企业知识,为AI内容生产提供智能支撑
{/* Error Banner */}
{error && (
)}
{/* Tab 切换 */}
企业知识库 ({enterpriseBases.length})
行业知识库 ({industryBases.length})
{/* ── 企业知识库 ── */}
{filteredBases.length === 0 ? (
setCreateDialogOpen(true)} />
) : (
{filteredBases.map((kb) => (
setExpandedKbId(expandedKbId === kb.id ? null : kb.id)}
onDelete={handleDeleteKb}
onUpload={openUploadDialog}
/>
))}
)}
{/* ── 行业知识库 ── */}
{filteredBases.length === 0 ? (
) : (
{filteredBases.map((kb) => (
{kb.name}
平台维护
{kb.description && (
{kb.description}
)}
{kb.document_count} 文档
))}
)}
{/* ── 创建知识库 Dialog ── */}
{/* ── 上传文档 Dialog ── */}
);
}