mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 14:31:35 +08:00
feat(translate): 添加拖拽上传文件功能
实现文件拖拽上传功能,包括拖拽区域高亮显示和提示文本 添加多文件上传错误提示和未知错误处理
This commit is contained in:
parent
25827cdf8d
commit
a6c4d1a68a
@ -3767,7 +3767,9 @@
|
||||
"label": "交换源语言与目标语言"
|
||||
},
|
||||
"files": {
|
||||
"drag_text": "拖放到此处",
|
||||
"error": {
|
||||
"multiple": "不允许上传多个文件",
|
||||
"too_large": "文件过大",
|
||||
"unknown": "读取文件内容失败"
|
||||
},
|
||||
|
||||
@ -9,6 +9,7 @@ import { LanguagesEnum, UNKNOWN } from '@renderer/config/translate'
|
||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||
import db from '@renderer/databases'
|
||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||
import { useDrag } from '@renderer/hooks/useDrag'
|
||||
import { useFiles } from '@renderer/hooks/useFiles'
|
||||
import { useOcr } from '@renderer/hooks/useOcr'
|
||||
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
|
||||
@ -20,6 +21,7 @@ import { setTranslating as setTranslatingAction } from '@renderer/store/runtime'
|
||||
import { setTranslatedContent as setTranslatedContentAction } from '@renderer/store/translate'
|
||||
import {
|
||||
type AutoDetectionMethod,
|
||||
FileMetadata,
|
||||
isSupportedOcrFile,
|
||||
type Model,
|
||||
type TranslateHistory,
|
||||
@ -27,6 +29,7 @@ import {
|
||||
} from '@renderer/types'
|
||||
import { runAsyncFunction } from '@renderer/utils'
|
||||
import { formatErrorMessage } from '@renderer/utils/error'
|
||||
import { getFilesFromDropEvent, getTextFromDropEvent } from '@renderer/utils/input'
|
||||
import {
|
||||
createInputScrollHandler,
|
||||
createOutputScrollHandler,
|
||||
@ -37,7 +40,7 @@ import { imageExts, MB, textExts } from '@shared/config/constant'
|
||||
import { Button, Flex, FloatButton, Popover, Tooltip, Typography } from 'antd'
|
||||
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
|
||||
import { isEmpty, throttle } from 'lodash'
|
||||
import { Check, FolderClock, Settings2 } from 'lucide-react'
|
||||
import { Check, FolderClock, Settings2, UploadIcon } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
@ -434,16 +437,9 @@ const TranslatePage: FC = () => {
|
||||
// 控制token估计
|
||||
const tokenCount = useMemo(() => estimateTextTokens(text + prompt), [prompt, text])
|
||||
|
||||
// 控制文件ocr
|
||||
const handleSelectFile = useCallback(async () => {
|
||||
if (selecting) return
|
||||
setIsProcessing(true)
|
||||
try {
|
||||
const [file] = await onSelectFile({ multipleSelections: false })
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
|
||||
// 统一的文件处理
|
||||
const processFile = useCallback(
|
||||
async (file: FileMetadata) => {
|
||||
// extensible
|
||||
const shouldOCR = isSupportedOcrFile(file)
|
||||
|
||||
@ -471,6 +467,21 @@ const TranslatePage: FC = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[ocr, t]
|
||||
)
|
||||
|
||||
// 点击上传文件按钮
|
||||
const handleSelectFile = useCallback(async () => {
|
||||
if (selecting) return
|
||||
setIsProcessing(true)
|
||||
try {
|
||||
const [file] = await onSelectFile({ multipleSelections: false })
|
||||
if (!file) {
|
||||
return
|
||||
}
|
||||
|
||||
return await processFile(file)
|
||||
} catch (e) {
|
||||
logger.error('Unknown error when selecting file.', e as Error)
|
||||
window.message.error(t('translate.files.error.unknown') + ': ' + formatErrorMessage(e))
|
||||
@ -478,10 +489,62 @@ const TranslatePage: FC = () => {
|
||||
clearFiles()
|
||||
setIsProcessing(false)
|
||||
}
|
||||
}, [clearFiles, ocr, onSelectFile, selecting, t])
|
||||
}, [clearFiles, onSelectFile, processFile, selecting, t])
|
||||
|
||||
// 拖动上传文件
|
||||
const onDrop = useCallback(
|
||||
async (e: React.DragEvent<HTMLDivElement>) => {
|
||||
setIsProcessing(true)
|
||||
// const supportedFiles = await filterSupportedFiles(_files, extensions)
|
||||
const data = await getTextFromDropEvent(e).catch((err) => {
|
||||
logger.error('getTextFromDropEvent', err)
|
||||
window.message.error({
|
||||
key: 'file_error',
|
||||
content: t('translate.files.error.unknown')
|
||||
})
|
||||
return null
|
||||
})
|
||||
if (data === null) {
|
||||
return
|
||||
}
|
||||
setText(text + data)
|
||||
|
||||
const droppedFiles = await getFilesFromDropEvent(e).catch((err) => {
|
||||
logger.error('handleDrop:', err)
|
||||
return null
|
||||
})
|
||||
|
||||
if (droppedFiles) {
|
||||
if (droppedFiles.length === 0) return
|
||||
if (droppedFiles.length > 1) {
|
||||
// 多文件上传时显示提示信息
|
||||
window.message.error({
|
||||
key: 'multiple_files',
|
||||
content: t('translate.files.error.multiple')
|
||||
})
|
||||
return
|
||||
}
|
||||
const file = droppedFiles[0]
|
||||
processFile(file)
|
||||
} else if (droppedFiles === null) {
|
||||
window.message.error({
|
||||
key: 'file_error',
|
||||
content: t('translate.files.error.unknown')
|
||||
})
|
||||
}
|
||||
setIsProcessing(false)
|
||||
},
|
||||
[processFile, t, text]
|
||||
)
|
||||
|
||||
const { isDragging, handleDragEnter, handleDragLeave, handleDragOver, handleDrop } = useDrag<HTMLDivElement>(onDrop)
|
||||
|
||||
return (
|
||||
<Container id="translate-page">
|
||||
<Container
|
||||
id="translate-page"
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDragOver={handleDragOver}>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none', gap: 10 }}>{t('translate.title')}</NavbarCenter>
|
||||
</Navbar>
|
||||
@ -543,7 +606,15 @@ const TranslatePage: FC = () => {
|
||||
</InnerOperationBar>
|
||||
</OperationBar>
|
||||
<AreaContainer>
|
||||
<InputContainer>
|
||||
<InputContainer
|
||||
style={isDragging ? { border: '2px dashed var(--color-primary)' } : undefined}
|
||||
onDrop={handleDrop}>
|
||||
{isDragging && (
|
||||
<InputContainerDraggingHintContainer>
|
||||
<UploadIcon color="var(--color-text-3)" />
|
||||
{t('translate.files.drag_text')}
|
||||
</InputContainerDraggingHintContainer>
|
||||
)}
|
||||
<FloatButton
|
||||
style={{ position: 'absolute', left: 8, bottom: 8 }}
|
||||
className="float-button"
|
||||
@ -663,6 +734,19 @@ const InputContainer = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const InputContainerDraggingHintContainer = styled.div`
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-text-3);
|
||||
`
|
||||
|
||||
const Textarea = styled(TextArea)`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user