yakub
feat: implement real-time source ingestion progress, chitchat intent classifier, and empty state fallback
fd1e711 | import { FileText, Globe, Youtube, Loader2, CheckCircle, AlertCircle } from 'lucide-react'; | |
| import { KnowledgeSource } from '../types'; | |
| interface SourceCardProps { | |
| source: KnowledgeSource; | |
| compact?: boolean; | |
| } | |
| const languageColors = { | |
| EN: 'bg-blue-500/10 text-blue-600 dark:text-blue-400', | |
| HI: 'bg-orange-500/10 text-orange-600 dark:text-orange-400', | |
| TE: 'bg-purple-500/10 text-purple-600 dark:text-purple-400', | |
| ES: 'bg-green-500/10 text-green-600 dark:text-green-400', | |
| FR: 'bg-pink-500/10 text-pink-600 dark:text-pink-400', | |
| DE: 'bg-yellow-500/10 text-yellow-600 dark:text-yellow-400', | |
| }; | |
| const statusIcons: Record<string, any> = { | |
| pending: Loader2, | |
| processing: Loader2, | |
| completed: CheckCircle, | |
| failed: AlertCircle, | |
| IDLE: Loader2, | |
| UPLOADING: Loader2, | |
| EXTRACTING: Loader2, | |
| CHUNKING: Loader2, | |
| EMBEDDING: Loader2, | |
| INDEXING: Loader2, | |
| }; | |
| const statusColors: Record<string, string> = { | |
| pending: 'text-gray-400', | |
| processing: 'text-blue-500 animate-spin', | |
| completed: 'text-green-500', | |
| failed: 'text-red-500', | |
| IDLE: 'text-gray-400', | |
| UPLOADING: 'text-[#6366F1] animate-spin', | |
| EXTRACTING: 'text-indigo-400 animate-spin', | |
| CHUNKING: 'text-blue-400 animate-spin', | |
| EMBEDDING: 'text-purple-400 animate-spin', | |
| INDEXING: 'text-amber-500 animate-spin', | |
| }; | |
| export function SourceCard({ source, compact = false }: SourceCardProps) { | |
| const Icon = source.type === 'pdf' ? FileText : source.type === 'web' ? Globe : Youtube; | |
| const StatusIcon = statusIcons[source.status] || Loader2; | |
| if (compact) { | |
| return ( | |
| <div className="p-3 rounded-lg bg-white/5 backdrop-blur-md border border-white/10 hover:border-[#6366F1] transition-colors group cursor-pointer"> | |
| <div className="flex items-start gap-3"> | |
| <div className="p-2 rounded-lg bg-white/5 border border-white/10 group-hover:border-[#6366F1] transition-colors"> | |
| <Icon className="w-3.5 h-3.5 text-white/60" /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <div className="flex items-start justify-between gap-2 mb-1"> | |
| <h4 className="text-xs text-white truncate"> | |
| {source.title} | |
| </h4> | |
| <StatusIcon className={`w-3 h-3 flex-shrink-0 ${statusColors[source.status] || 'text-gray-400'}`} /> | |
| </div> | |
| <div className="flex items-center gap-1.5"> | |
| <span className={`px-1.5 py-0.5 rounded text-[10px] ${languageColors[source.language]}`}> | |
| {source.language} | |
| </span> | |
| {source.chunkCount !== undefined && source.chunkCount > 0 && ( | |
| <span className="text-[10px] text-gray-500 dark:text-gray-400"> | |
| {source.chunkCount} chunks | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <div className="p-5 rounded-xl bg-white/5 backdrop-blur-md border border-white/10 hover:border-[#6366F1] hover:shadow-lg transition-all group cursor-pointer"> | |
| <div className="flex items-start gap-4"> | |
| <div className="p-3 rounded-xl bg-white/5 border border-white/10 group-hover:border-[#6366F1] transition-colors"> | |
| <Icon className="w-5 h-5 text-white/60" /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <div className="flex items-start justify-between gap-3 mb-2"> | |
| <h3 className="text-white truncate"> | |
| {source.title} | |
| </h3> | |
| <StatusIcon className={`w-4 h-4 flex-shrink-0 ${statusColors[source.status] || 'text-gray-400'}`} /> | |
| </div> | |
| <div className="flex items-center gap-2 mb-3"> | |
| <span className={`px-2 py-1 rounded-md text-xs ${languageColors[source.language]}`}> | |
| {source.language} | |
| </span> | |
| <span className="text-xs text-gray-500 dark:text-gray-400 capitalize font-medium"> | |
| {source.status.toLowerCase()} {source.progress_percentage !== undefined && source.progress_percentage > 0 && source.progress_percentage < 100 ? ` (${source.progress_percentage}%)` : ''} | |
| </span> | |
| </div> | |
| <div className="flex items-center gap-4 text-xs text-gray-500 dark:text-gray-400"> | |
| {source.chunkCount !== undefined && source.chunkCount > 0 && ( | |
| <span>{source.chunkCount} chunks</span> | |
| )} | |
| {source.metadata?.pageCount && ( | |
| <span>{source.metadata.pageCount} pages</span> | |
| )} | |
| {source.metadata?.domain && ( | |
| <span className="truncate">{source.metadata.domain}</span> | |
| )} | |
| {source.metadata?.duration && ( | |
| <span>{source.metadata.duration}</span> | |
| )} | |
| </div> | |
| {source.lastProcessed && ( | |
| <div className="text-xs text-gray-400 dark:text-gray-500 mt-2"> | |
| Processed {source.lastProcessed.toLocaleDateString()} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |