refactor(Knowledge): enhance progress tracking in UI components

This commit is contained in:
suyao
2025-03-28 16:02:34 +08:00
parent 0d794cae36
commit aa44396a3e
4 changed files with 92 additions and 114 deletions
+4 -3
View File
@@ -9,8 +9,8 @@ import { TextItem } from 'pdfjs-dist/types/src/display/api'
import BaseOcrProvider from './BaseOcrProvider'
export default class MacSysOcrProvider extends BaseOcrProvider {
private readonly BATCH_SIZE = 5
private readonly CONCURRENCY = 5
private readonly BATCH_SIZE = 4
private readonly CONCURRENCY = 2
private readonly MIN_TEXT_LENGTH = 1000
constructor(provider: OcrProvider) {
super(provider)
@@ -40,6 +40,7 @@ export default class MacSysOcrProvider extends BaseOcrProvider {
// Process batch
const ocrResults = await MacOCR.recognizeBatchFromBuffer(pageBuffers, {
maxThreads: 4,
ocrOptions: {
recognitionLevel: MacOCR.RECOGNITION_LEVEL_ACCURATE
}
@@ -103,7 +104,7 @@ export default class MacSysOcrProvider extends BaseOcrProvider {
await new Promise<void>((resolve, reject) => {
writeStream.end(() => {
Logger.info('[OCR] OCR process completed successfully')
Logger.info(`[OCR] OCR process completed successfully for ${file.origin_name}`)
resolve()
})
writeStream.on('error', reject)
+7 -54
View File
@@ -1,4 +1,3 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { db } from '@renderer/databases/index'
import KnowledgeQueue from '@renderer/queue/KnowledgeQueue'
import FileManager from '@renderer/services/FileManager'
@@ -21,7 +20,7 @@ import {
} from '@renderer/store/knowledge'
import { FileType, KnowledgeBase, KnowledgeItem, ProcessingStatus } from '@renderer/types'
import { runAsyncFunction } from '@renderer/utils'
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { v4 as uuidv4 } from 'uuid'
@@ -189,62 +188,18 @@ export const useKnowledge = (baseId: string) => {
}
// 获取特定项目的处理状态
const getProcessingStatus = (itemId: string) => {
return base?.items.find((item) => item.id === itemId)?.processingStatus
}
const getProcessingStatus = useCallback(
(itemId: string) => {
return base?.items.find((item) => item.id === itemId)?.processingStatus
},
[base?.items]
)
// 获取特定类型的所有处理项
const getProcessingItemsByType = (type: 'file' | 'url' | 'note') => {
return base?.items.filter((item) => item.type === type && item.processingStatus !== undefined) || []
}
// 获取目录处理进度
const getDirectoryProcessingPercent = (itemId?: string) => {
const [percent, setPercent] = useState<number>(0)
useEffect(() => {
if (!itemId) {
return
}
const cleanup = window.electron.ipcRenderer.on(
'directory-processing-percent',
(_, { itemId: id, percent }: { itemId: string; percent: number }) => {
if (itemId === id) {
setPercent(percent)
}
}
)
return () => {
cleanup()
}
}, [itemId])
return percent
}
// 获取文件ocr处理进度
const getFileOcrProgress = (itemId: string) => {
const [progress, setProgress] = useState<number>(0)
useEffect(() => {
const cleanup = window.electron.ipcRenderer.on(
'file-ocr-progress',
(_, { itemId: id, progress }: { itemId: string; progress: number }) => {
if (itemId === id) {
setProgress(progress)
}
}
)
return () => {
cleanup()
}
}, [itemId])
return progress
}
// 清除已完成的项目
const clearCompleted = () => {
dispatch(clearCompletedProcessing({ baseId }))
@@ -327,8 +282,6 @@ export const useKnowledge = (baseId: string) => {
refreshItem,
getProcessingStatus,
getProcessingItemsByType,
getDirectoryProcessingPercent,
getFileOcrProgress,
clearCompleted,
clearAll,
removeItem,
@@ -21,7 +21,7 @@ import { getProviderName } from '@renderer/services/ProviderService'
import { FileType, FileTypes, KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { bookExts, documentExts, textExts, thirdPartyApplicationExts } from '@shared/config/constant'
import { Alert, Button, Card, Divider, Dropdown, message, Tag, Tooltip, Typography, Upload } from 'antd'
import { FC } from 'react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@@ -39,6 +39,25 @@ interface KnowledgeContentProps {
const fileTypes = [...bookExts, ...thirdPartyApplicationExts, ...documentExts, ...textExts]
const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
const { t } = useTranslation()
const [progressMap, setProgressMap] = useState<Map<string, number>>(new Map())
useEffect(() => {
const handlers = [
window.electron.ipcRenderer.on('file-ocr-progress', (_, { itemId, progress }) => {
console.log('[Progress] File OCR:', itemId, progress)
setProgressMap((prev) => new Map(prev).set(itemId, progress))
}),
window.electron.ipcRenderer.on('directory-processing-percent', (_, { itemId, percent }) => {
console.log('[Progress] Directory:', itemId, percent)
setProgressMap((prev) => new Map(prev).set(itemId, percent))
})
]
return () => {
handlers.forEach((cleanup) => cleanup())
}
}, [])
const {
base,
@@ -54,8 +73,6 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
addSitemap,
removeItem,
getProcessingStatus,
getDirectoryProcessingPercent,
getFileOcrProgress,
addNote,
addDirectory,
updateItem
@@ -69,13 +86,6 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
return null
}
const getProgressingPercentForItem = (itemId: string) => getDirectoryProcessingPercent(itemId)
const getFileOcrProgressForItem = (itemId: string) => {
console.log('[KnowledgeContent] getFileOcrProgressForItem:', itemId)
return getFileOcrProgress(itemId)
}
const handleAddFile = () => {
if (disabled) {
return
@@ -282,8 +292,8 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
getProcessingPercent={getFileOcrProgressForItem}
type="file"
progress={progressMap.get(item.id)}
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
@@ -320,8 +330,8 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
sourceId={item.id}
base={base}
getProcessingStatus={getProcessingStatus}
getProcessingPercent={getProgressingPercentForItem}
type="directory"
progress={progressMap.get(item.id)}
/>
</StatusIconWrapper>
<Button type="text" danger onClick={() => removeItem(item)} icon={<DeleteOutlined />} />
@@ -1,7 +1,8 @@
import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'
import { KnowledgeBase, ProcessingStatus } from '@renderer/types'
import { Progress, Tooltip } from 'antd'
import { FC } from 'react'
import React from 'react'
import { FC, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@@ -9,64 +10,67 @@ interface StatusIconProps {
sourceId: string
base: KnowledgeBase
getProcessingStatus: (sourceId: string) => ProcessingStatus | undefined
getProcessingPercent?: (sourceId: string) => number | undefined
type: string
progress?: number
}
const StatusIcon: FC<StatusIconProps> = ({ sourceId, base, getProcessingStatus, getProcessingPercent, type }) => {
const StatusIcon: FC<StatusIconProps> = ({ sourceId, base, getProcessingStatus, type, progress = 0 }) => {
const { t } = useTranslation()
const status = getProcessingStatus(sourceId)
const percent = getProcessingPercent?.(sourceId)
const item = base.items.find((item) => item.id === sourceId)
const errorText = item?.processingError
if (!status) {
if (item?.uniqueId) {
const statusDisplay = useMemo(() => {
if (!status) {
if (item?.uniqueId) {
return (
<Tooltip title={t('knowledge.status_completed')} placement="left">
<CheckCircleOutlined style={{ color: '#52c41a' }} />
</Tooltip>
)
}
return (
<Tooltip title={t('knowledge.status_completed')} placement="left">
<CheckCircleOutlined style={{ color: '#52c41a' }} />
<Tooltip title={t('knowledge.status_new')} placement="left">
<StatusDot $status="new" />
</Tooltip>
)
}
return (
<Tooltip title={t('knowledge.status_new')} placement="left">
<StatusDot $status="new" />
</Tooltip>
)
}
switch (status) {
case 'pending':
return (
<Tooltip title={t('knowledge.status_pending')} placement="left">
<StatusDot $status="pending" />
</Tooltip>
)
switch (status) {
case 'pending':
return (
<Tooltip title={t('knowledge.status_pending')} placement="left">
<StatusDot $status="pending" />
</Tooltip>
)
case 'processing': {
return type === 'directory' || type === 'file' ? (
<Progress type="circle" size={14} percent={Number(percent?.toFixed(0))} />
) : (
<Tooltip title={t('knowledge.status_processing')} placement="left">
<StatusDot $status="processing" />
</Tooltip>
)
case 'processing': {
return type === 'directory' || type === 'file' ? (
<Progress type="circle" size={14} percent={Number(progress?.toFixed(0))} />
) : (
<Tooltip title={t('knowledge.status_processing')} placement="left">
<StatusDot $status="processing" />
</Tooltip>
)
}
case 'completed':
return (
<Tooltip title={t('knowledge.status_completed')} placement="left">
<CheckCircleOutlined style={{ color: '#52c41a' }} />
</Tooltip>
)
case 'failed':
return (
<Tooltip title={errorText || t('knowledge.status_failed')} placement="left">
<CloseCircleOutlined style={{ color: '#ff4d4f' }} />
</Tooltip>
)
default:
return null
}
case 'completed':
return (
<Tooltip title={t('knowledge.status_completed')} placement="left">
<CheckCircleOutlined style={{ color: '#52c41a' }} />
</Tooltip>
)
case 'failed':
return (
<Tooltip title={errorText || t('knowledge.status_failed')} placement="left">
<CloseCircleOutlined style={{ color: '#ff4d4f' }} />
</Tooltip>
)
default:
return null
}
}, [status, item?.uniqueId, type, progress, errorText, t])
return statusDisplay
}
const StatusDot = styled.div<{ $status: 'pending' | 'processing' | 'new' }>`
@@ -91,4 +95,14 @@ const StatusDot = styled.div<{ $status: 'pending' | 'processing' | 'new' }>`
}
`
export default StatusIcon
export default React.memo(StatusIcon, (prevProps, nextProps) => {
return (
prevProps.sourceId === nextProps.sourceId &&
prevProps.type === nextProps.type &&
prevProps.base.id === nextProps.base.id &&
prevProps.progress === nextProps.progress &&
prevProps.getProcessingStatus(prevProps.sourceId) === nextProps.getProcessingStatus(nextProps.sourceId) &&
prevProps.base.items.find((item) => item.id === prevProps.sourceId)?.processingError ===
nextProps.base.items.find((item) => item.id === nextProps.sourceId)?.processingError
)
})