feat(ui): enhance ClearDocumentsDialog with loading spinner and timeout protection

- Add loading spinner animation during document clearing operation
- Implement 30-second timeout protection to prevent hanging operations
- Disable all interactive controls during clearing to prevent duplicate requests
- Add comprehensive error handling with automatic state reset
This commit is contained in:
yangdx 2025-08-17 01:33:39 +08:00
parent 45365ff6ef
commit e064534941
6 changed files with 70 additions and 7 deletions

View file

@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from 'react'
import { useState, useCallback, useEffect, useRef } from 'react'
import Button from '@/components/ui/Button'
import {
Dialog,
@ -15,7 +15,7 @@ import { toast } from 'sonner'
import { errorMessage } from '@/lib/utils'
import { clearDocuments, clearCache } from '@/api/lightrag'
import { EraserIcon, AlertTriangleIcon } from 'lucide-react'
import { EraserIcon, AlertTriangleIcon, Loader2Icon } from 'lucide-react'
import { useTranslation } from 'react-i18next'
// 简单的Label组件
@ -43,18 +43,51 @@ export default function ClearDocumentsDialog({ onDocumentsCleared }: ClearDocume
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'
// 超时常量 (30秒)
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) return
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()
@ -86,8 +119,15 @@ export default function ClearDocumentsDialog({ onDocumentsCleared }: ClearDocume
} 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, clearCacheOption, setOpen, t, onDocumentsCleared])
}, [isConfirmEnabled, isClearing, clearCacheOption, setOpen, t, onDocumentsCleared, CLEAR_TIMEOUT])
return (
<Dialog open={open} onOpenChange={setOpen}>
@ -125,6 +165,7 @@ export default function ClearDocumentsDialog({ onDocumentsCleared }: ClearDocume
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setConfirmText(e.target.value)}
placeholder={t('documentPanel.clearDocuments.confirmPlaceholder')}
className="w-full"
disabled={isClearing}
/>
</div>
@ -133,6 +174,7 @@ export default function ClearDocumentsDialog({ onDocumentsCleared }: ClearDocume
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')}
@ -141,15 +183,26 @@ export default function ClearDocumentsDialog({ onDocumentsCleared }: ClearDocume
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
<Button
variant="outline"
onClick={() => setOpen(false)}
disabled={isClearing}
>
{t('common.cancel')}
</Button>
<Button
variant="destructive"
onClick={handleClear}
disabled={!isConfirmEnabled}
disabled={!isConfirmEnabled || isClearing}
>
{t('documentPanel.clearDocuments.confirmButton')}
{isClearing ? (
<>
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
{t('documentPanel.clearDocuments.clearing')}
</>
) : (
t('documentPanel.clearDocuments.confirmButton')
)}
</Button>
</DialogFooter>
</DialogContent>

View file

@ -50,6 +50,8 @@
"confirmPlaceholder": "اكتب yes للتأكيد",
"clearCache": "مسح كاش نموذج اللغة",
"confirmButton": "نعم",
"clearing": "جارٍ المسح...",
"timeout": "انتهت مهلة عملية المسح، يرجى المحاولة مرة أخرى",
"success": "تم مسح المستندات بنجاح",
"cacheCleared": "تم مسح ذاكرة التخزين المؤقت بنجاح",
"cacheClearFailed": "فشل مسح ذاكرة التخزين المؤقت:\n{{error}}",

View file

@ -50,6 +50,8 @@
"confirmPlaceholder": "Type yes to confirm",
"clearCache": "Clear LLM cache",
"confirmButton": "YES",
"clearing": "Clearing...",
"timeout": "Clear operation timed out, please try again",
"success": "Documents cleared successfully",
"cacheCleared": "Cache cleared successfully",
"cacheClearFailed": "Failed to clear cache:\n{{error}}",

View file

@ -50,6 +50,8 @@
"confirmPlaceholder": "Tapez yes pour confirmer",
"clearCache": "Effacer le cache LLM",
"confirmButton": "OUI",
"clearing": "Effacement en cours...",
"timeout": "L'opération d'effacement a expiré, veuillez réessayer",
"success": "Documents effacés avec succès",
"cacheCleared": "Cache effacé avec succès",
"cacheClearFailed": "Échec de l'effacement du cache :\n{{error}}",

View file

@ -50,6 +50,8 @@
"confirmPlaceholder": "输入 yes 确认",
"clearCache": "清空LLM缓存",
"confirmButton": "确定",
"clearing": "正在清除...",
"timeout": "清除操作超时,请重试",
"success": "文档清空成功",
"cacheCleared": "缓存清空成功",
"cacheClearFailed": "清空缓存失败:\n{{error}}",

View file

@ -50,6 +50,8 @@
"confirmPlaceholder": "輸入 yes 以確認",
"clearCache": "清空 LLM 快取",
"confirmButton": "確定",
"clearing": "正在清除...",
"timeout": "清除操作逾時,請重試",
"success": "文件清空成功",
"cacheCleared": "快取清空成功",
"cacheClearFailed": "清空快取失敗:\n{{error}}",