refactor(Knowledge): enhance progress tracking in UI components
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user