Files
course-agent-od/web/src/app/(main)/admin/knowledge/page.tsx
T
pengxiao b8561e04c6 fix: source list scroll, citation targeting, and page UI polish
- Replace ScrollArea with native overflow-y-auto for reliable mouse wheel scrolling
- Scope source DOM IDs by message ID to fix cross-round citation jumps
- Improve profile, settings, forum, knowledge page layouts
- Update mobile navigation and chat interface

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 22:05:08 +08:00

140 lines
5.4 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { adminAPI } from "@/lib/api";
import { format } from "date-fns";
import { Database, Trash2, RefreshCw } from "lucide-react";
interface AdminKB {
id: number;
name: string;
description: string | null;
owner_name: string;
is_system: boolean;
document_count: number;
chunk_count: number;
created_at: string;
}
export default function KnowledgeAdminPage() {
const [kbs, setKbs] = useState<AdminKB[]>([]);
const [loading, setLoading] = useState(true);
const [confirmDelete, setConfirmDelete] = useState<number | null>(null);
const load = () => {
setLoading(true);
adminAPI.listKnowledgeBases()
.then(setKbs)
.catch(console.error)
.finally(() => setLoading(false));
};
useEffect(() => { load(); }, []);
const handleDelete = async (kbId: number) => {
try {
await adminAPI.deleteKnowledgeBase(kbId);
setConfirmDelete(null);
setKbs((prev) => prev.filter((kb) => kb.id !== kbId));
} catch (e) { console.error(e); }
};
if (loading) {
return <div className="text-muted-foreground text-sm py-12 text-center">...</div>;
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Database className="w-5 h-5 text-primary" />
<h2 className="text-lg font-semibold"></h2>
<span className="text-sm text-muted-foreground"> {kbs.length} </span>
</div>
<button
onClick={load}
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
>
<RefreshCw className="w-3.5 h-3.5" />
</button>
</div>
{kbs.length === 0 ? (
<div className="rounded-xl border py-12 text-center text-muted-foreground text-sm">
</div>
) : (
<div className="rounded-xl border overflow-hidden">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/30">
<th className="text-left px-4 py-3 font-medium"></th>
<th className="text-left px-4 py-3 font-medium"></th>
<th className="text-center px-4 py-3 font-medium"></th>
<th className="text-center px-4 py-3 font-medium"></th>
<th className="text-center px-4 py-3 font-medium"></th>
<th className="text-center px-4 py-3 font-medium"></th>
<th className="text-left px-4 py-3 font-medium"></th>
<th className="text-center px-4 py-3 font-medium"></th>
</tr>
</thead>
<tbody>
{kbs.map((kb) => (
<tr key={kb.id} className="border-b last:border-0 hover:bg-muted/20">
<td className="px-4 py-3 font-medium">{kb.name}</td>
<td className="px-4 py-3 text-muted-foreground text-xs max-w-48 truncate">
{kb.description || "—"}
</td>
<td className="px-4 py-3 text-center text-muted-foreground">{kb.owner_name}</td>
<td className="px-4 py-3 text-center">
<span
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
kb.is_system
? "bg-primary/10 text-primary"
: "bg-muted text-muted-foreground"
}`}
>
{kb.is_system ? "系统" : "用户"}
</span>
</td>
<td className="px-4 py-3 text-center font-mono tabular-nums">{kb.document_count}</td>
<td className="px-4 py-3 text-center font-mono tabular-nums">{kb.chunk_count}</td>
<td className="px-4 py-3 text-muted-foreground text-xs">
{format(new Date(kb.created_at), "yyyy/M/d HH:mm")}
</td>
<td className="px-4 py-3 text-center">
{confirmDelete === kb.id ? (
<div className="flex items-center justify-center gap-1">
<button
onClick={() => handleDelete(kb.id)}
className="text-xs px-2 py-1 bg-destructive text-destructive-foreground rounded hover:bg-destructive/80"
>
</button>
<button
onClick={() => setConfirmDelete(null)}
className="text-xs px-2 py-1 border rounded hover:bg-accent"
>
</button>
</div>
) : (
<button
onClick={() => setConfirmDelete(kb.id)}
className="text-destructive hover:text-destructive/80 transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}