| import { useState, useCallback, useEffect, useRef } from 'react' |
| import Button from '@/components/ui/Button' |
| import { |
| Dialog, |
| DialogContent, |
| DialogDescription, |
| DialogHeader, |
| DialogTitle, |
| DialogTrigger, |
| DialogFooter |
| } from '@/components/ui/Dialog' |
| import Input from '@/components/ui/Input' |
| import Checkbox from '@/components/ui/Checkbox' |
| import { toast } from 'sonner' |
| import { errorMessage } from '@/lib/utils' |
| import { clearDocuments, clearCache } from '@/api/lightrag' |
|
|
| import { EraserIcon, AlertTriangleIcon, Loader2Icon } from 'lucide-react' |
| import { useTranslation } from 'react-i18next' |
|
|
| |
| const Label = ({ |
| htmlFor, |
| className, |
| children, |
| ...props |
| }: React.LabelHTMLAttributes<HTMLLabelElement>) => ( |
| <label |
| htmlFor={htmlFor} |
| className={className} |
| {...props} |
| > |
| {children} |
| </label> |
| ) |
|
|
| interface ClearDocumentsDialogProps { |
| onDocumentsCleared?: () => Promise<void> |
| } |
|
|
| export default function ClearDocumentsDialog({ onDocumentsCleared }: ClearDocumentsDialogProps) { |
| const { t } = useTranslation() |
| const [open, setOpen] = useState(false) |
| const [confirmText, setConfirmText] = useState('') |
| const [clearCacheOption, setClearCacheOption] = useState(false) |
| const [isClearing, setIsClearing] = useState(false) |
| const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null) |
| const isConfirmEnabled = confirmText.toLowerCase() === 'yes' |
|
|
| |
| const CLEAR_TIMEOUT = 30000 |
|
|
| |
| useEffect(() => { |
| if (!open) { |
| setConfirmText('') |
| setClearCacheOption(false) |
| setIsClearing(false) |
|
|
| |
| if (timeoutRef.current) { |
| clearTimeout(timeoutRef.current) |
| timeoutRef.current = null |
| } |
| } |
| }, [open]) |
|
|
| |
| useEffect(() => { |
| return () => { |
| |
| if (timeoutRef.current) { |
| clearTimeout(timeoutRef.current) |
| } |
| } |
| }, []) |
|
|
| const handleClear = useCallback(async () => { |
| if (!isConfirmEnabled || isClearing) return |
|
|
| setIsClearing(true) |
|
|
| |
| timeoutRef.current = setTimeout(() => { |
| if (isClearing) { |
| toast.error(t('documentPanel.clearDocuments.timeout')) |
| setIsClearing(false) |
| setConfirmText('') |
| } |
| }, CLEAR_TIMEOUT) |
|
|
| try { |
| const result = await clearDocuments() |
|
|
| if (result.status !== 'success') { |
| toast.error(t('documentPanel.clearDocuments.failed', { message: result.message })) |
| setConfirmText('') |
| return |
| } |
|
|
| toast.success(t('documentPanel.clearDocuments.success')) |
|
|
| if (clearCacheOption) { |
| try { |
| await clearCache() |
| toast.success(t('documentPanel.clearDocuments.cacheCleared')) |
| } catch (cacheErr) { |
| toast.error(t('documentPanel.clearDocuments.cacheClearFailed', { error: errorMessage(cacheErr) })) |
| } |
| } |
|
|
| |
| if (onDocumentsCleared) { |
| onDocumentsCleared().catch(console.error) |
| } |
|
|
| |
| setOpen(false) |
| } catch (err) { |
| toast.error(t('documentPanel.clearDocuments.error', { error: errorMessage(err) })) |
| setConfirmText('') |
| } finally { |
| |
| if (timeoutRef.current) { |
| clearTimeout(timeoutRef.current) |
| timeoutRef.current = null |
| } |
| setIsClearing(false) |
| } |
| }, [isConfirmEnabled, isClearing, clearCacheOption, setOpen, t, onDocumentsCleared, CLEAR_TIMEOUT]) |
|
|
| return ( |
| <Dialog open={open} onOpenChange={setOpen}> |
| <DialogTrigger asChild> |
| <Button variant="outline" side="bottom" tooltip={t('documentPanel.clearDocuments.tooltip')} size="sm"> |
| <EraserIcon/> {t('documentPanel.clearDocuments.button')} |
| </Button> |
| </DialogTrigger> |
| <DialogContent className="sm:max-w-xl" onCloseAutoFocus={(e) => e.preventDefault()}> |
| <DialogHeader> |
| <DialogTitle className="flex items-center gap-2 text-red-500 dark:text-red-400 font-bold"> |
| <AlertTriangleIcon className="h-5 w-5" /> |
| {t('documentPanel.clearDocuments.title')} |
| </DialogTitle> |
| <DialogDescription className="pt-2"> |
| {t('documentPanel.clearDocuments.description')} |
| </DialogDescription> |
| </DialogHeader> |
| |
| <div className="text-red-500 dark:text-red-400 font-semibold mb-4"> |
| {t('documentPanel.clearDocuments.warning')} |
| </div> |
| <div className="mb-4"> |
| {t('documentPanel.clearDocuments.confirm')} |
| </div> |
| |
| <div className="space-y-4"> |
| <div className="space-y-2"> |
| <Label htmlFor="confirm-text" className="text-sm font-medium"> |
| {t('documentPanel.clearDocuments.confirmPrompt')} |
| </Label> |
| <Input |
| id="confirm-text" |
| value={confirmText} |
| onChange={(e: React.ChangeEvent<HTMLInputElement>) => setConfirmText(e.target.value)} |
| placeholder={t('documentPanel.clearDocuments.confirmPlaceholder')} |
| className="w-full" |
| disabled={isClearing} |
| /> |
| </div> |
| |
| <div className="flex items-center space-x-2"> |
| <Checkbox |
| id="clear-cache" |
| checked={clearCacheOption} |
| onCheckedChange={(checked: boolean | 'indeterminate') => setClearCacheOption(checked === true)} |
| disabled={isClearing} |
| /> |
| <Label htmlFor="clear-cache" className="text-sm font-medium cursor-pointer"> |
| {t('documentPanel.clearDocuments.clearCache')} |
| </Label> |
| </div> |
| </div> |
| |
| <DialogFooter> |
| <Button |
| variant="outline" |
| onClick={() => setOpen(false)} |
| disabled={isClearing} |
| > |
| {t('common.cancel')} |
| </Button> |
| <Button |
| variant="destructive" |
| onClick={handleClear} |
| disabled={!isConfirmEnabled || isClearing} |
| > |
| {isClearing ? ( |
| <> |
| <Loader2Icon className="mr-2 h-4 w-4 animate-spin" /> |
| {t('documentPanel.clearDocuments.clearing')} |
| </> |
| ) : ( |
| t('documentPanel.clearDocuments.confirmButton') |
| )} |
| </Button> |
| </DialogFooter> |
| </DialogContent> |
| </Dialog> |
| ) |
| } |
|
|