Better UX

This commit is contained in:
Xin Wang
2026-02-04 18:36:40 +08:00
parent 47207dab19
commit b608c395c7
14 changed files with 877 additions and 403 deletions

View File

@@ -1,3 +1,4 @@
import React, { useState, useRef } from 'react';
import { Search, Plus, FileText, Upload, ArrowLeft, CloudUpload, File as FileIcon, X } from 'lucide-react';
import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Card, Dialog } from '../components/UI';
@@ -10,6 +11,8 @@ export const KnowledgeBasePage: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [kbs, setKbs] = useState(mockKnowledgeBases);
const [isUploadOpen, setIsUploadOpen] = useState(false);
const [isCreateKbOpen, setIsCreateKbOpen] = useState(false);
const [newKbName, setNewKbName] = useState('');
const filteredKbs = kbs.filter(kb => kb.name.toLowerCase().includes(searchTerm.toLowerCase()));
@@ -22,33 +25,52 @@ export const KnowledgeBasePage: React.FC = () => {
setIsUploadOpen(true);
};
const handleCreateKb = () => {
if (!newKbName.trim()) return;
const newKb: KnowledgeBase = {
id: `kb_${Date.now()}`,
name: newKbName.trim(),
creator: 'Admin User',
createdAt: new Date().toISOString().split('T')[0],
documents: []
};
setKbs([newKb, ...kbs]);
setIsCreateKbOpen(false);
setNewKbName('');
};
if (view === 'detail' && selectedKb) {
return (
<>
<div className="py-4 pb-10">
<KnowledgeBaseDetail
kb={selectedKb}
onBack={() => setView('list')}
onImport={handleImportClick}
/>
<UploadModal isOpen={isUploadOpen} onClose={() => setIsUploadOpen(false)} />
</>
</div>
);
}
return (
<div className="space-y-6 animate-in fade-in">
<div className="space-y-6 animate-in fade-in py-4 pb-10">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold tracking-tight"></h1>
<h1 className="text-2xl font-bold tracking-tight text-white"></h1>
</div>
<div className="flex items-center space-x-2 bg-card/50 p-2 rounded-lg border border-white/5 shadow-sm w-full md:w-1/3">
<Search className="h-4 w-4 text-muted-foreground ml-2" />
<Input
placeholder="搜索知识库名称..."
className="border-0 shadow-none bg-transparent focus-visible:ring-0"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
{/* Search Bar - Layout aligned with History Page and width filled */}
<div className="bg-card/50 p-4 rounded-lg border border-white/5 shadow-sm">
<div className="relative w-full">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="搜索知识库名称..."
className="pl-9 border-0 bg-white/5 w-full"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@@ -63,7 +85,7 @@ export const KnowledgeBasePage: React.FC = () => {
<FileText className="h-6 w-6" />
</div>
</div>
<h3 className="text-lg font-semibold group-hover:text-primary transition-colors">{kb.name}</h3>
<h3 className="text-lg font-semibold group-hover:text-primary transition-colors text-white">{kb.name}</h3>
<div className="mt-4 space-y-1 text-sm text-muted-foreground">
<p>: {kb.documents.length}</p>
<p>: {kb.creator}</p>
@@ -73,12 +95,44 @@ export const KnowledgeBasePage: React.FC = () => {
</Card>
))}
{/* Add New Placeholer */}
<div className="border border-dashed border-white/10 rounded-xl p-6 flex flex-col items-center justify-center text-muted-foreground hover:bg-white/5 hover:border-primary/30 transition-all cursor-pointer min-h-[200px]">
{/* Add New Placeholder */}
<div
onClick={() => setIsCreateKbOpen(true)}
className="border border-dashed border-white/10 rounded-xl p-6 flex flex-col items-center justify-center text-muted-foreground hover:bg-white/5 hover:border-primary/30 transition-all cursor-pointer min-h-[200px]"
>
<Plus className="h-8 w-8 mb-2 opacity-50" />
<span></span>
</div>
</div>
{/* New Knowledge Base Dialog */}
<Dialog
isOpen={isCreateKbOpen}
onClose={() => setIsCreateKbOpen(false)}
title="新建知识库"
footer={
<>
<Button variant="ghost" onClick={() => setIsCreateKbOpen(false)}></Button>
<Button onClick={handleCreateKb} disabled={!newKbName.trim()}></Button>
</>
}
>
<div className="space-y-4">
<div className="space-y-1.5">
<label className="text-[10px] font-black text-muted-foreground uppercase tracking-widest block"></label>
<Input
value={newKbName}
onChange={(e) => setNewKbName(e.target.value)}
placeholder="请输入知识库名称..."
autoFocus
onKeyDown={(e) => e.key === 'Enter' && handleCreateKb()}
/>
</div>
<p className="text-xs text-muted-foreground">
AI
</p>
</div>
</Dialog>
</div>
);
};
@@ -99,7 +153,7 @@ const KnowledgeBaseDetail: React.FC<{
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<h1 className="text-2xl font-bold">{kb.name}</h1>
<h1 className="text-2xl font-bold text-white">{kb.name}</h1>
<p className="text-sm text-muted-foreground"> {kb.createdAt} · by {kb.creator}</p>
</div>
</div>
@@ -110,7 +164,7 @@ const KnowledgeBaseDetail: React.FC<{
<Card className="overflow-hidden border-white/5">
<div className="p-4 border-b border-white/5 flex justify-between items-center bg-white/5">
<h3 className="font-medium"></h3>
<h3 className="font-medium text-white"></h3>
<div className="w-64">
<Input
placeholder="搜索文档..."
@@ -132,21 +186,18 @@ const KnowledgeBaseDetail: React.FC<{
<tbody>
{filteredDocs.length > 0 ? filteredDocs.map(doc => (
<TableRow key={doc.id}>
<TableCell className="font-medium flex items-center">
<TableCell className="font-medium flex items-center text-white">
<FileText className="h-4 w-4 mr-2 text-primary"/> {doc.name}
</TableCell>
<TableCell>{doc.size}</TableCell>
<TableCell>{doc.uploadDate}</TableCell>
<TableCell className="text-muted-foreground">{doc.size}</TableCell>
<TableCell className="text-muted-foreground">{doc.uploadDate}</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm" className="text-destructive hover:text-destructive/80"></Button>
</TableCell>
</TableRow>
)) : (
<TableRow>
<TableCell className="text-center py-8 text-muted-foreground"></TableCell>
<TableCell> </TableCell>
<TableCell> </TableCell>
<TableCell> </TableCell>
<TableCell colSpan={4} className="text-center py-8 text-muted-foreground"></TableCell>
</TableRow>
)}
</tbody>
@@ -176,7 +227,6 @@ const UploadModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpe
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
// Add new files to existing state
setFiles(prev => [...prev, ...Array.from(e.dataTransfer.files)]);
}
};
@@ -224,7 +274,7 @@ const UploadModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpe
<p className="text-sm text-muted-foreground text-center">
<span className="font-semibold text-primary"></span>
</p>
<p className="text-xs text-muted-foreground mt-1"> PDF, DOCX, TXT (Max 10MB)</p>
<p className="text-xs text-muted-foreground mt-1 text-white/50"> PDF, DOCX, TXT (Max 10MB)</p>
</div>
{files.length > 0 && (
@@ -233,10 +283,10 @@ const UploadModal: React.FC<{ isOpen: boolean; onClose: () => void }> = ({ isOpe
<div key={idx} className="flex items-center justify-between p-2 rounded-md bg-white/5 border border-white/5">
<div className="flex items-center space-x-2 overflow-hidden">
<FileIcon className="h-4 w-4 text-primary shrink-0" />
<span className="text-sm truncate max-w-[200px]">{file.name}</span>
<span className="text-sm truncate max-w-[200px] text-white">{file.name}</span>
<span className="text-xs text-muted-foreground">({(file.size / 1024).toFixed(1)} KB)</span>
</div>
<button onClick={() => removeFile(idx)} className="text-muted-foreground hover:text-destructive">
<button onClick={() => removeFile(idx)} className="text-muted-foreground hover:text-destructive transition-colors">
<X className="h-4 w-4" />
</button>
</div>