import { useState, useEffect, useRef, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { AlignLeft, AlignCenter, AlignRight, Link, Loader2, CheckCircle2, AlertCircle, Play, Square } from 'lucide-react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/Dialog' import Button from '@/components/ui/Button' import Progress from '@/components/ui/Progress' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/Select' import { getOrphanConnectionStatus, startOrphanConnection, cancelOrphanConnection, OrphanConnectionStatus } from '@/api/lightrag' import { errorMessage } from '@/lib/utils' import { cn } from '@/lib/utils' type DialogPosition = 'left' | 'center' | 'right' interface OrphanConnectionDialogProps { open: boolean onOpenChange: (open: boolean) => void } export default function OrphanConnectionDialog({ open, onOpenChange }: OrphanConnectionDialogProps) { const { t } = useTranslation() const [position, setPosition] = useState('center') const [status, setStatus] = useState(null) const [isStarting, setIsStarting] = useState(false) const [maxDegree, setMaxDegree] = useState(0) const [isCancelling, setIsCancelling] = useState(false) const messagesEndRef = useRef(null) const messagesContainerRef = useRef(null) // Scroll to bottom of messages const scrollToBottom = useCallback(() => { if (messagesContainerRef.current) { messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight } }, []) // Poll status every 2 seconds when dialog is open useEffect(() => { if (!open) return const fetchStatus = async () => { try { const data = await getOrphanConnectionStatus() setStatus(data) } catch (err) { console.error('Failed to fetch orphan connection status:', err) } } // Fetch immediately fetchStatus() // Then poll every 2 seconds const interval = setInterval(fetchStatus, 2000) return () => clearInterval(interval) }, [open]) // Auto-scroll when new messages arrive useEffect(() => { if (status?.history_messages && status.history_messages.length > 0) { scrollToBottom() } }, [status?.history_messages, scrollToBottom]) // Reset position when dialog opens useEffect(() => { if (open) { setPosition('center') } }, [open]) // Handle start const handleStart = async () => { try { setIsStarting(true) const result = await startOrphanConnection(3, maxDegree) if (result.status === 'already_running') { toast.info(t('graphPanel.orphanConnection.alreadyRunning')) } else { toast.success(t('graphPanel.orphanConnection.started')) } } catch (err) { const errMsg = errorMessage(err) toast.error(t('graphPanel.orphanConnection.error', { error: errMsg })) } finally { setIsStarting(false) } } // Handle cancel const handleCancel = async () => { try { setIsCancelling(true) const result = await cancelOrphanConnection() if (result.status === 'cancellation_requested') { toast.success(t('graphPanel.orphanConnection.cancelSuccess')) } else { toast.info(t('graphPanel.orphanConnection.notRunning')) } } catch (err) { const errMsg = errorMessage(err) toast.error(t('graphPanel.orphanConnection.error', { error: errMsg })) } finally { setIsCancelling(false) } } // Calculate progress percentage const progress = status?.total_orphans ? Math.round((status.processed_orphans / status.total_orphans) * 100) : 0 return ( {t('graphPanel.orphanConnection.description')} {t('graphPanel.orphanConnection.title')} {/* Position control buttons */}
{/* Content */}
{/* Description */}

{t('graphPanel.orphanConnection.explanation')}

{/* Max Degree Selector */}
{/* Status Section */} {status && (
{/* Status indicator */}
0 ? 'bg-green-50 dark:bg-green-950 border-green-200 dark:border-green-800' : 'bg-zinc-50 dark:bg-zinc-900 border-zinc-200 dark:border-zinc-700' )}> {status.busy ? ( ) : status.connections_made > 0 ? ( ) : ( )}

{status.busy ? status.job_name || t('graphPanel.orphanConnection.running') : status.total_orphans > 0 ? t('graphPanel.orphanConnection.completed') : t('graphPanel.orphanConnection.ready') }

{status.busy && status.cancellation_requested && (

{t('graphPanel.orphanConnection.cancellationPending')}

)}
{/* Progress bar (only when busy or has results) */} {(status.busy || status.total_orphans > 0) && (
{t('graphPanel.orphanConnection.progress')}: {status.processed_orphans}/{status.total_orphans} {progress}%
)} {/* Stats */} {(status.total_orphans > 0 || status.connections_made > 0) && (

{status.total_orphans}

{t('graphPanel.orphanConnection.totalOrphans')}

{status.processed_orphans}

{t('graphPanel.orphanConnection.processed')}

{status.connections_made}

{t('graphPanel.orphanConnection.connectionsMade')}

)} {/* Activity Log */} {status.history_messages && status.history_messages.length > 0 && (

{t('graphPanel.orphanConnection.activityLog')}

{status.history_messages.map((msg, idx) => (
{msg}
))}
)}
)} {/* Action Buttons */}
{status?.busy ? ( ) : ( )}
) }