diff --git a/frontend/src/components/dialog/image-dialog/index.js b/frontend/src/components/dialog/image-dialog/index.js
index 9aa5058c2e..ef461d0bde 100644
--- a/frontend/src/components/dialog/image-dialog/index.js
+++ b/frontend/src/components/dialog/image-dialog/index.js
@@ -16,7 +16,7 @@ const SIDE_PANEL_EXPANDED_WIDTH = 300;
const ImageDialog = ({ repoID, repoInfo, enableRotate: oldEnableRotate = true, imageItems, imageIndex, closeImagePopup, moveToPrevImage, moveToNextImage, onDeleteImage, onRotateImage, isCustomPermission }) => {
const [expanded, setExpanded] = useState(false);
- const { enableOCR, enableMetadata, canModify, onOCR: onOCRAPI, OCRSuccessCallBack } = useMetadataAIOperations();
+ const { enableOCR, enableMetadata, canModify, onOCRByImageDialog } = useMetadataAIOperations();
const downloadImage = useCallback((url) => {
location.href = url;
@@ -52,7 +52,7 @@ const ImageDialog = ({ repoID, repoInfo, enableRotate: oldEnableRotate = true, i
const isSystemFolder = SYSTEM_FOLDERS.find(folderPath => mainImg.parentDir.startsWith(folderPath));
let onOCR = null;
if (enableOCR && enableMetadata && canModify && !isSystemFolder) {
- onOCR = () => onOCRAPI({ parentDir: mainImg.parentDir, fileName: mainImg.name }, { success_callback: OCRSuccessCallBack });
+ onOCR = () => onOCRByImageDialog({ parentDir: mainImg.parentDir, fileName: mainImg.name });
}
const renderSidePanel = () => {
diff --git a/frontend/src/components/dialog/move-dirent-dialog.js b/frontend/src/components/dialog/move-dirent-dialog.js
index 6cd2f86fa6..980d44c278 100644
--- a/frontend/src/components/dialog/move-dirent-dialog.js
+++ b/frontend/src/components/dialog/move-dirent-dialog.js
@@ -22,7 +22,7 @@ const propTypes = {
onAddFolder: PropTypes.func,
};
-class MoveDirent extends React.Component {
+class MoveDirentDialog extends React.Component {
constructor(props) {
super(props);
@@ -382,6 +382,6 @@ class MoveDirent extends React.Component {
}
}
-MoveDirent.propTypes = propTypes;
+MoveDirentDialog.propTypes = propTypes;
-export default MoveDirent;
+export default MoveDirentDialog;
diff --git a/frontend/src/components/file-view/file-view.js b/frontend/src/components/file-view/file-view.js
index 34e5b50ed2..a623306194 100644
--- a/frontend/src/components/file-view/file-view.js
+++ b/frontend/src/components/file-view/file-view.js
@@ -13,9 +13,7 @@ import FileToolbar from './file-toolbar';
import CommentPanel from './comment-panel';
import OnlyofficeFileToolbar from './onlyoffice-file-toolbar';
import EmbeddedFileDetails from '../dirent-detail/embedded-file-details';
-import { MetadataStatusProvider } from '../../hooks';
-import { CollaboratorsProvider } from '../../metadata';
-import { TagsProvider } from '../../tag/hooks';
+import { MetadataMiddlewareProvider, MetadataStatusProvider } from '../../hooks';
import Loading from '../loading';
import '../../css/file-view.css';
@@ -180,17 +178,15 @@ class FileView extends React.Component {
}
{isDetailsPanelOpen && (
-
-
-
-
-
+
+
+
)}
diff --git a/frontend/src/components/toolbar/table-files-toolbar.js b/frontend/src/components/toolbar/table-files-toolbar.js
index dc839e7f9c..b7d97e8157 100644
--- a/frontend/src/components/toolbar/table-files-toolbar.js
+++ b/frontend/src/components/toolbar/table-files-toolbar.js
@@ -84,6 +84,7 @@ const TableFilesToolbar = ({ repoID }) => {
const isDescribableFile = canModifyRow && Utils.isDescriptionSupportedFile(fileName);
const isImage = Utils.imageCheck(fileName);
const isVideo = Utils.videoCheck(fileName);
+ const isPDF = Utils.pdfCheck(fileName);
const descriptionColumn = getColumnByKey(columns, PRIVATE_COLUMN_KEY.FILE_DESCRIPTION);
const aiOptions = [];
@@ -95,7 +96,7 @@ const TableFilesToolbar = ({ repoID }) => {
aiOptions.push(GENERATE_DESCRIPTION);
}
- if (enableOCR && isImage) {
+ if (enableOCR && (isImage || isPDF)) {
aiOptions.push(OCR);
}
diff --git a/frontend/src/hooks/index.js b/frontend/src/hooks/index.js
index 6037889dc7..0ede4cad11 100644
--- a/frontend/src/hooks/index.js
+++ b/frontend/src/hooks/index.js
@@ -1,2 +1,3 @@
-export { MetadataStatusProvider, useMetadataStatus } from './metadata-status';
export { DownloadFileProvider } from './download-file';
+export { MetadataMiddlewareProvider } from './metadata-middleware';
+export { MetadataStatusProvider, useMetadataStatus } from './metadata-status';
diff --git a/frontend/src/hooks/metadata-ai-operation.js b/frontend/src/hooks/metadata-ai-operation.js
index 04c3d01f7d..e4894189aa 100644
--- a/frontend/src/hooks/metadata-ai-operation.js
+++ b/frontend/src/hooks/metadata-ai-operation.js
@@ -1,9 +1,11 @@
-import React, { useContext, useCallback, useMemo } from 'react';
+import React, { useContext, useCallback, useMemo, useState, useRef } from 'react';
import metadataAPI from '../metadata/api';
import { Utils } from '../utils/utils';
import toaster from '../components/toast';
import { PRIVATE_COLUMN_KEY, EVENT_BUS_TYPE } from '../metadata/constants';
import { gettext, lang } from '../utils/constants';
+import OCRResultDialog from '../metadata/components/dialog/ocr-result-dialog';
+import FileTagsDialog from '../metadata/components/dialog/file-tags-dialog';
// This hook provides content related to metadata ai operation
const MetadataAIOperationsContext = React.createContext(null);
@@ -17,33 +19,48 @@ export const MetadataAIOperationsProvider = ({
repoInfo,
children
}) => {
+ const [isOcrResultDialogShow, setOcrResultDialogShow] = useState(false);
+ const [isFileTagsDialogShow, setFileTagsDialogShow] = useState(false);
+
+ const recordRef = useRef(null);
+ const opCallBack = useRef(null);
+
const permission = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw', [repoInfo]);
const canModify = useMemo(() => permission === 'rw', [permission]);
- const OCRSuccessCallBack = useCallback(({ parentDir, fileName, ocrResult } = {}) => {
- if (!ocrResult) return;
- const update = { [PRIVATE_COLUMN_KEY.OCR]: ocrResult };
- metadataAPI.modifyRecord(repoID, { parentDir, fileName }, update).then(res => {
- const eventBus = window?.sfMetadataContext?.eventBus;
- eventBus && eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, { parentDir, fileName }, update);
- });
- }, [repoID]);
+ const closeFileTagsDialog = useCallback(() => {
+ recordRef.current = null;
+ opCallBack.current = null;
+ setFileTagsDialogShow(false);
+ }, []);
- const onOCR = useCallback(({ parentDir, fileName }, { success_callback, fail_callback } = {}) => {
- const filePath = Utils.joinPath(parentDir, fileName);
- const inProgressToaster = toaster.notifyInProgress(gettext('Extracting text by AI...'), { duration: null });
- metadataAPI.ocr(repoID, filePath).then(res => {
- const ocrResult = res.data.ocr_result;
- const validResult = Array.isArray(ocrResult) && ocrResult.length > 0 ? JSON.stringify(ocrResult) : null;
- inProgressToaster.close();
- toaster.success(gettext('Text extracted'));
- success_callback && success_callback({ parentDir, fileName, ocrResult: validResult });
- }).catch(error => {
- inProgressToaster.close();
- const errorMessage = gettext('Failed to extract text');
- toaster.danger(errorMessage);
- fail_callback && fail_callback();
- });
+ const closeOcrResultDialog = useCallback(() => {
+ recordRef.current = null;
+ opCallBack.current = null;
+ setOcrResultDialogShow(false);
+ }, []);
+
+ const onOCR = useCallback((record, { success_callback }) => {
+ recordRef.current = record;
+ opCallBack.current = success_callback;
+ setOcrResultDialogShow(true);
+ }, []);
+
+ const onOCRByImageDialog = useCallback(({ parentDir, fileName } = {}) => {
+ recordRef.current = {
+ [PRIVATE_COLUMN_KEY.PARENT_DIR]: parentDir,
+ [PRIVATE_COLUMN_KEY.FILE_NAME]: fileName,
+ };
+
+ opCallBack.current = (description) => {
+ const update = { [PRIVATE_COLUMN_KEY.FILE_DESCRIPTION]: description };
+ metadataAPI.modifyRecord(repoID, { parentDir, fileName }, update).then(res => {
+ const eventBus = window?.sfMetadataContext?.eventBus;
+ eventBus && eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, { parentDir, fileName }, update);
+ eventBus && eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, { parentDir, fileName }, update);
+ });
+ };
+ setOcrResultDialogShow(true);
}, [repoID]);
const generateDescription = useCallback(({ parentDir, fileName }, { success_callback, fail_callback } = {}) => {
@@ -106,23 +123,11 @@ export const MetadataAIOperationsProvider = ({
});
}, [repoID]);
-
- const extractText = useCallback(({ parentDir, fileName }, { success_callback, fail_callback } = {}) => {
- const filePath = Utils.joinPath(parentDir, fileName);
- const inProgressToaster = toaster.notifyInProgress(gettext('Extracting text by AI...'), { duration: null });
- metadataAPI.extractText(repoID, filePath).then(res => {
- console.log(res)
- const extractedText = res?.data?.text || res.data.text || '';
- inProgressToaster.close();
- success_callback && success_callback({ parentDir, fileName, extractedText });
- }).catch(error => {
- inProgressToaster.close();
- const errorMessage = gettext('Failed to extract text');
- toaster.danger(errorMessage);
- fail_callback && fail_callback();
- });
- }, [repoID]);
-
+ const generateFileTags = useCallback((record, { success_callback }) => {
+ recordRef.current = record;
+ opCallBack.current = success_callback;
+ setFileTagsDialogShow(true);
+ }, []);
return (
{children}
+ {isFileTagsDialogShow && (
+
+ )}
+ {isOcrResultDialogShow && (
+
+ )}
);
};
diff --git a/frontend/src/hooks/metadata-middleware.js b/frontend/src/hooks/metadata-middleware.js
new file mode 100644
index 0000000000..82401e4e14
--- /dev/null
+++ b/frontend/src/hooks/metadata-middleware.js
@@ -0,0 +1,38 @@
+import React, { useContext } from 'react';
+import { MetadataAIOperationsProvider } from './metadata-ai-operation';
+import { TagsProvider } from '../tag/hooks';
+import { CollaboratorsProvider } from '../metadata';
+import { useMetadataStatus } from './metadata-status';
+
+const MetadataMiddlewareContext = React.createContext(null);
+
+export const MetadataMiddlewareProvider = ({ repoID, currentPath, repoInfo, onTreeNodeClick, tagsChangedCallback, children }) => {
+ const { enableMetadata, enableOCR, enableTags, tagsLang } = useMetadataStatus();
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+};
+
+export const useMetadataMiddleware = () => {
+ const context = useContext(MetadataMiddlewareContext);
+ if (!context) {
+ throw new Error('\'MetadataMiddlewareContext\' is null');
+ }
+ return context;
+};
diff --git a/frontend/src/hooks/metadata-status.js b/frontend/src/hooks/metadata-status.js
index d5e1f5f6dd..95d30acee1 100644
--- a/frontend/src/hooks/metadata-status.js
+++ b/frontend/src/hooks/metadata-status.js
@@ -2,9 +2,10 @@ import React, { useContext, useEffect, useCallback, useState, useMemo } from 're
import metadataAPI from '../metadata/api';
import { Utils } from '../utils/utils';
import toaster from '../components/toast';
-import { MetadataAIOperationsProvider } from './metadata-ai-operation';
import Loading from '../components/loading';
+const { enableSeafileAI, enableSeafileOCR } = window.app.config;
+
// This hook provides content related to seahub interaction, such as whether to enable extended attributes
const MetadataStatusContext = React.createContext(null);
@@ -64,8 +65,8 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta
setEnableTags(enableTags);
setTagsLang(tagsLang || 'en');
setDetailsSettings(JSON.parse(detailsSettings));
- setEnableOCR(enableOCR);
- setEnableFaceRecognition(enableFaceRecognition);
+ setEnableOCR(enableSeafileOCR && enableOCR);
+ setEnableFaceRecognition(enableSeafileAI && enableFaceRecognition);
setEnableMetadata(enableMetadata);
setLoading(false);
}).catch(error => {
@@ -154,16 +155,7 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, sta
}}
>
{!isLoading && (
-
- {children}
-
+ <>{children}>
)}
);
diff --git a/frontend/src/index.js b/frontend/src/index.js
index a8827052b8..bbe13e5967 100644
--- a/frontend/src/index.js
+++ b/frontend/src/index.js
@@ -5,8 +5,7 @@ import { I18nextProvider } from 'react-i18next';
import i18n from './_i18n/i18n-seafile-editor';
import MarkdownEditor from './pages/markdown-editor';
import Loading from './components/loading';
-import { MetadataStatusProvider } from './hooks';
-import { CollaboratorsProvider } from './metadata';
+import { MetadataMiddlewareProvider, MetadataStatusProvider } from './hooks';
import './index.css';
@@ -17,9 +16,9 @@ root.render(
}>
-
+
-
+
diff --git a/frontend/src/metadata/api.js b/frontend/src/metadata/api.js
index 863c3a4107..190f19e840 100644
--- a/frontend/src/metadata/api.js
+++ b/frontend/src/metadata/api.js
@@ -406,15 +406,6 @@ class MetadataManagerAPI {
return this.req.post(url, params);
};
- extractText = (repoID, filePath) => {
- const url = this.server + '/api/v2.1/ai/extract-text/';
- const params = {
- path: filePath,
- repo_id: repoID,
- };
- return this.req.post(url, params);
- };
-
}
const metadataAPI = new MetadataManagerAPI();
diff --git a/frontend/src/metadata/components/dialog/ocr-result-dialog/index.css b/frontend/src/metadata/components/dialog/ocr-result-dialog/index.css
new file mode 100644
index 0000000000..c7350d6683
--- /dev/null
+++ b/frontend/src/metadata/components/dialog/ocr-result-dialog/index.css
@@ -0,0 +1,8 @@
+.sf-metadata-ocr-file-dialog .sf-centered-loading {
+ border-top: 1px solid #e9ecef;
+}
+
+.sf-metadata-ocr-file-dialog .seafile-multicolor-icon-save {
+ height: 15px;
+ width: 15px;
+}
diff --git a/frontend/src/metadata/components/dialog/ocr-result-dialog/index.js b/frontend/src/metadata/components/dialog/ocr-result-dialog/index.js
new file mode 100644
index 0000000000..9f4ca037a7
--- /dev/null
+++ b/frontend/src/metadata/components/dialog/ocr-result-dialog/index.js
@@ -0,0 +1,139 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
+import { I18nextProvider } from 'react-i18next';
+import { SimpleEditor, getBrowserInfo, LongTextModal, BrowserTip, slateToMdString, MarkdownPreview } from '@seafile/seafile-editor';
+import CenteredLoading from '../../../../components/centered-loading';
+import toaster from '../../../../components/toast';
+import { gettext, lang } from '../../../../utils/constants';
+import { getFileNameFromRecord, getParentDirFromRecord } from '../../../utils/cell';
+import { Utils } from '../../../../utils/utils';
+import i18n from '../../../../_i18n/i18n-seafile-editor';
+import Icon from '../../../../components/icon';
+
+import './index.css';
+
+const OCRResultDialog = ({ record, onToggle, saveToDescription }) => {
+ const [isLoading, setLoading] = useState(true);
+ const [isFullScreen, setIsFullScreen] = useState(false);
+ const [dialogStyle, setDialogStyle] = useState({});
+
+ const ocrResult = useRef(null);
+
+ const parentDir = useMemo(() => getParentDirFromRecord(record), [record]);
+ const fileName = useMemo(() => getFileNameFromRecord(record), [record]);
+
+ const editorRef = useRef(null);
+
+ const { isValidBrowser, isWindowsWechat } = useMemo(() => {
+ return getBrowserInfo(true);
+ }, []);
+
+ const onFullScreenToggle = useCallback(() => {
+ let containerStyle = {};
+ if (!isFullScreen) {
+ containerStyle = {
+ width: '100%',
+ height: '100%',
+ top: 0,
+ border: 'none'
+ };
+ }
+ setIsFullScreen(!isFullScreen);
+ setDialogStyle(containerStyle);
+ }, [isFullScreen]);
+
+ const onContainerKeyDown = useCallback((event) => {
+ event.stopPropagation();
+ if (event.keyCode === 27) {
+ event.stopPropagation();
+ event.preventDefault();
+ onToggle();
+ }
+ }, [onToggle]);
+
+ const onSave = useCallback(() => {
+ const newContent = editorRef.current?.getSlateValue();
+ const value = slateToMdString(newContent);
+ saveToDescription(value);
+ onToggle();
+ }, [saveToDescription, onToggle]);
+
+ useEffect(() => {
+ const path = window.sfMetadataContext.canModifyRow(record) ? Utils.joinPath(parentDir, fileName) : '';
+ if (path === '') {
+ setLoading(false);
+ return;
+ }
+ window.sfMetadataContext.ocr(path).then(res => {
+ const result = res.data?.ocr_result || '';
+ ocrResult.current = result.replaceAll('\n\n', '\n').replaceAll('\n', '\n\n');
+ setLoading(false);
+ }).catch(error => {
+ const errorMessage = gettext('Failed to extract text');
+ toaster.danger(errorMessage);
+ setLoading(false);
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+
+
+
+
+
{gettext('OCR result')}
+
+
+
+
+
+
+
+
+
+
+ {!isValidBrowser &&
}
+
+
+ {isLoading ? (
+
+ ) : (
+ <>
+ {isWindowsWechat ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+
+
+
+ );
+};
+
+OCRResultDialog.propTypes = {
+ record: PropTypes.object,
+ onToggle: PropTypes.func,
+ saveToDescription: PropTypes.func,
+};
+
+export default OCRResultDialog;
diff --git a/frontend/src/metadata/components/metadata-details/ai-icon.js b/frontend/src/metadata/components/metadata-details/ai-icon.js
index 3462b3214d..0e2f5a79d4 100644
--- a/frontend/src/metadata/components/metadata-details/ai-icon.js
+++ b/frontend/src/metadata/components/metadata-details/ai-icon.js
@@ -9,7 +9,6 @@ import { getFileNameFromRecord, getFileObjIdFromRecord, getParentDirFromRecord,
import { getColumnByKey } from '../../utils/column';
import { PRIVATE_COLUMN_KEY } from './constants';
import { useMetadataAIOperations } from '../../../hooks/metadata-ai-operation';
-import FileTagsDialog from '../dialog/file-tags-dialog';
import { checkIsDir } from '../../utils/row';
const OPERATION = {
@@ -17,17 +16,15 @@ const OPERATION = {
OCR: 'ocr',
FILE_TAGS: 'file-tags',
FILE_DETAIL: 'file-detail',
- EXTRACT_TEXT: 'extract-text',
};
const AIIcon = () => {
const [isMenuShow, setMenuShow] = useState(false);
- const [isFileTagsDialogShow, setFileTagsDialogShow] = useState(false);
- const { enableMetadata, enableTags } = useMetadataStatus();
- const { canModifyRecord, columns, record, onChange, onLocalRecordChange, updateFileTags } = useMetadataDetails();
- const { onOCR, generateDescription, extractFileDetails, extractText } = useMetadataAIOperations();
+ const { enableMetadata, enableTags, enableOCR } = useMetadataStatus();
+ const { canModifyRecord, columns, record, onChange, onLocalRecordChange, updateFileTags, updateDescription } = useMetadataDetails();
+ const { generateDescription, extractFileDetails, onOCR, generateFileTags } = useMetadataAIOperations();
const options = useMemo(() => {
if (!canModifyRecord || !record || checkIsDir(record)) return [];
@@ -47,6 +44,10 @@ const AIIcon = () => {
});
}
+ if (enableOCR && (isImage || isPdf)) {
+ list.push({ value: OPERATION.OCR, label: gettext('Extract text'), record });
+ }
+
if (isImage || isVideo) {
list.push({ value: OPERATION.FILE_DETAIL, label: gettext('Extract file detail'), record });
}
@@ -55,11 +56,8 @@ const AIIcon = () => {
list.push({ value: OPERATION.FILE_TAGS, label: gettext('Generate file tags'), record });
}
- if (isImage || isPdf) {
- list.push({ value: OPERATION.EXTRACT_TEXT, label: gettext('Extract text'), record });
- }
return list;
- }, [enableTags, canModifyRecord, columns, record]);
+ }, [enableTags, enableOCR, canModifyRecord, columns, record]);
const onToggle = useCallback((event) => {
event && event.preventDefault();
@@ -67,10 +65,6 @@ const AIIcon = () => {
setMenuShow(!isMenuShow);
}, [isMenuShow]);
- const toggleFileTagsDialog = useCallback(() => {
- setFileTagsDialogShow(!isFileTagsDialogShow);
- }, [isFileTagsDialogShow]);
-
const handelOperation = useCallback((op) => {
const { value: opType, record } = op;
const recordId = getRecordIdFromRecord(record);
@@ -89,16 +83,15 @@ const AIIcon = () => {
break;
}
case OPERATION.OCR: {
- onOCR({ parentDir, fileName }, {
- success_callback: ({ ocrResult }) => {
- if (!ocrResult) return;
- onChange && onChange(PRIVATE_COLUMN_KEY.OCR, JSON.stringify(ocrResult));
- },
+ onOCR(record, {
+ success_callback: updateDescription
});
break;
}
case OPERATION.FILE_TAGS: {
- setFileTagsDialogShow(true);
+ generateFileTags(record, {
+ success_callback: updateFileTags
+ });
break;
}
case OPERATION.FILE_DETAIL: {
@@ -128,56 +121,37 @@ const AIIcon = () => {
});
break;
}
- case OPERATION.EXTRACT_TEXT: {
- extractText({ parentDir, fileName }, {
- success_callback: ({ extractedText }) => {
- console.log(extractedText)
- },
- });
- break;
- }
default: {
setMenuShow(false);
break;
}
}
- }, [columns, generateDescription, onOCR, extractFileDetails, onChange, onLocalRecordChange]);
-
- const renderDropdown = useCallback(() => {
- if (!enableMetadata || !canModifyRecord || !record || options.length === 0) return null;
- return (
-
-
-
-
-
-
- {isMenuShow && (
-
-
- {options.map(op => ( handelOperation(op)}>{op.label}))}
-
-
- )}
-
- );
- }, [isMenuShow, enableMetadata, canModifyRecord, record, options, onToggle, handelOperation]);
+ }, [columns, generateDescription, onOCR, generateFileTags, extractFileDetails, onChange, onLocalRecordChange, updateFileTags, updateDescription]);
+ if (!enableMetadata || !canModifyRecord || !record || options.length === 0) return null;
return (
- <>
- {renderDropdown()}
- {isFileTagsDialogShow && (
-
+
+
+
+
+
+
+ {isMenuShow && (
+
+
+ {options.map(op => ( handelOperation(op)}>{op.label}))}
+
+
)}
- >
+
);
};
diff --git a/frontend/src/metadata/components/metadata-details/constants.js b/frontend/src/metadata/components/metadata-details/constants.js
index 7ab69780e3..eb8ec71264 100644
--- a/frontend/src/metadata/components/metadata-details/constants.js
+++ b/frontend/src/metadata/components/metadata-details/constants.js
@@ -22,7 +22,6 @@ export const NOT_DISPLAY_COLUMN_KEYS = [
PRIVATE_COLUMN_KEY.EXCLUDED_FACE_LINKS,
PRIVATE_COLUMN_KEY.INCLUDED_FACE_LINKS,
PRIVATE_COLUMN_KEY.FACE_VECTORS,
- PRIVATE_COLUMN_KEY.OCR,
PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED,
];
diff --git a/frontend/src/metadata/constants/column/common.js b/frontend/src/metadata/constants/column/common.js
index ff0cd7d36b..a3772ec711 100644
--- a/frontend/src/metadata/constants/column/common.js
+++ b/frontend/src/metadata/constants/column/common.js
@@ -13,7 +13,6 @@ export const NOT_DISPLAY_COLUMN_KEYS = [
PRIVATE_COLUMN_KEY.EXCLUDED_FACE_LINKS,
PRIVATE_COLUMN_KEY.INCLUDED_FACE_LINKS,
PRIVATE_COLUMN_KEY.FACE_VECTORS,
- PRIVATE_COLUMN_KEY.OCR,
PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED,
];
diff --git a/frontend/src/metadata/constants/column/private.js b/frontend/src/metadata/constants/column/private.js
index 98be13afd9..ed357757a0 100644
--- a/frontend/src/metadata/constants/column/private.js
+++ b/frontend/src/metadata/constants/column/private.js
@@ -40,9 +40,6 @@ export const PRIVATE_COLUMN_KEY = {
// tag
TAGS: '_tags',
- // ocr
- OCR: '_ocr',
-
// location
LOCATION_TRANSLATED: '_location_translated'
};
@@ -81,7 +78,6 @@ export const PRIVATE_COLUMN_KEYS = [
PRIVATE_COLUMN_KEY.FACE_VECTORS,
PRIVATE_COLUMN_KEY.FILE_RATE,
PRIVATE_COLUMN_KEY.TAGS,
- PRIVATE_COLUMN_KEY.OCR,
PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED,
];
diff --git a/frontend/src/metadata/constants/event-bus-type.js b/frontend/src/metadata/constants/event-bus-type.js
index 91b9dc8c80..59451c5b9d 100644
--- a/frontend/src/metadata/constants/event-bus-type.js
+++ b/frontend/src/metadata/constants/event-bus-type.js
@@ -46,7 +46,6 @@ export const EVENT_BUS_TYPE = {
UPDATE_RECORD_DETAILS: 'update_record_details',
UPDATE_FACE_RECOGNITION: 'update_face_recognition',
GENERATE_DESCRIPTION: 'generate_description',
- EXTRACT_TEXT: 'extract_text',
OCR: 'ocr',
// metadata
diff --git a/frontend/src/metadata/hooks/metadata-details.js b/frontend/src/metadata/hooks/metadata-details.js
index 9e9d10ecca..1bdb197279 100644
--- a/frontend/src/metadata/hooks/metadata-details.js
+++ b/frontend/src/metadata/hooks/metadata-details.js
@@ -1,4 +1,6 @@
import React, { useContext, useEffect, useCallback, useState, useMemo, useRef } from 'react';
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
import metadataAPI from '../api';
import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
@@ -6,13 +8,18 @@ import { useMetadataStatus } from '../../hooks/metadata-status';
import { SYSTEM_FOLDERS } from '../../constants';
import Column from '../model/column';
import { normalizeFields } from '../components/metadata-details/utils';
-import { CellType, EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../constants';
-import { getCellValueByColumn, getColumnOptionNamesByIds, getColumnOptionNameById, getRecordIdFromRecord, getServerOptions } from '../utils/cell';
+import { CellType, EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY, UTC_FORMAT_DEFAULT } from '../constants';
+import {
+ getCellValueByColumn, getColumnOptionNamesByIds, getColumnOptionNameById, getRecordIdFromRecord,
+ getServerOptions, getFileNameFromRecord, getParentDirFromRecord
+} from '../utils/cell';
import tagsAPI from '../../tag/api';
import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../utils/column';
import ObjectUtils from '../../utils/object';
import { NOT_DISPLAY_COLUMN_KEYS } from '../components/metadata-details/constants';
+dayjs.extend(utc);
+
const MetadataDetailsContext = React.createContext(null);
export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, direntDetail, direntType, modifyLocalFileTags, onErrMessage, children }) => {
@@ -50,10 +57,16 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
return [...exitColumnsOrder, ...newColumns];
}, [originColumns, detailsSettings]);
- const onLocalRecordChange = useCallback((recordId, updates) => {
- if (getRecordIdFromRecord(record) !== recordId) return;
- const newRecord = { ...record, ...updates };
- setRecord(newRecord);
+ const onLocalRecordChange = useCallback(({ recordId, parentDir, fileName }, updates) => {
+ if (getRecordIdFromRecord(record) === recordId || (getParentDirFromRecord(record) === parentDir && getFileNameFromRecord(record) === fileName)) {
+ const newRecord = {
+ ...record,
+ [PRIVATE_COLUMN_KEY.MTIME]: dayjs().utc().format(UTC_FORMAT_DEFAULT),
+ [PRIVATE_COLUMN_KEY.LAST_MODIFIER]: window.sfMetadataContext.getUsername(),
+ ...updates,
+ };
+ setRecord(newRecord);
+ }
}, [record]);
const onChange = useCallback((fieldKey, newValue) => {
@@ -70,7 +83,7 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
setRecord({ ...record, ...update });
if (window?.sfMetadataContext?.eventBus) {
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, { recordId }, update);
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, recordId, update);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, { recordId }, update);
}
}).catch(error => {
const errorMsg = Utils.getErrorMsg(error);
@@ -134,6 +147,10 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
});
}, [repoID, record, modifyLocalFileTags]);
+ const updateDescription = useCallback((description) => {
+ onChange(PRIVATE_COLUMN_KEY.FILE_DESCRIPTION, description);
+ }, [onChange]);
+
const saveColumns = useCallback((columns) => {
modifyDetailsSettings && modifyDetailsSettings({ columns: columns.map(c => ({ key: c.key, shown: c.shown })) });
}, [modifyDetailsSettings]);
@@ -219,6 +236,7 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
updateFileTags,
modifyHiddenColumns,
modifyColumnOrder,
+ updateDescription,
}}
>
{children}
diff --git a/frontend/src/metadata/hooks/metadata-view.js b/frontend/src/metadata/hooks/metadata-view.js
index 6f4abd67d8..d1ac21e0ff 100644
--- a/frontend/src/metadata/hooks/metadata-view.js
+++ b/frontend/src/metadata/hooks/metadata-view.js
@@ -38,7 +38,7 @@ export const MetadataViewProvider = ({
const { collaborators } = useCollaborators();
const { isBeingBuilt, setIsBeingBuilt } = useMetadata();
- const { onOCR, generateDescription, extractFilesDetails, faceRecognition, extractText } = useMetadataAIOperations();
+ const { onOCR: OCRAPI, generateDescription, extractFilesDetails, faceRecognition, generateFileTags: generateFileTagsAPI } = useMetadataAIOperations();
const tableChanged = useCallback(() => {
setMetadata(storeRef.current.data);
@@ -381,38 +381,35 @@ export const MetadataViewProvider = ({
});
}, [modifyRecords, generateDescription]);
- const ocr = useCallback((record) => {
+ const onOCR = useCallback((record) => {
const parentDir = getParentDirFromRecord(record);
const fileName = getFileNameFromRecord(record);
- if (!Utils.imageCheck(fileName)) return;
-
- const ocrResultColumnKey = PRIVATE_COLUMN_KEY.OCR;
- let idOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [ocrResultColumnKey]: record[ocrResultColumnKey] } };
- let idOriginalOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [ocrResultColumnKey]: record[ocrResultColumnKey] } };
- onOCR({ parentDir, fileName }, {
- success_callback: ({ ocrResult }) => {
- if (!ocrResult) return;
+ if (!fileName || !parentDir) return;
+ const descriptionColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
+ let idOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [descriptionColumnKey]: record[descriptionColumnKey] } };
+ let idOriginalOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [descriptionColumnKey]: record[descriptionColumnKey] } };
+ OCRAPI(record, {
+ success_callback: (description) => {
+ if (!description) return;
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
const recordIds = [updateRecordId];
let idRecordUpdates = {};
let idOriginalRecordUpdates = {};
- idRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
- idOriginalRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
+ idRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description || null };
+ idOriginalRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description || null };
modifyRecords(recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData);
- },
+ }
});
- }, [modifyRecords, onOCR]);
+ }, [modifyRecords, OCRAPI]);
- const updateExtractText = useCallback((record) => {
+ const generateFileTags = useCallback((record) => {
const parentDir = getParentDirFromRecord(record);
const fileName = getFileNameFromRecord(record);
if (!fileName || !parentDir) return;
- extractText({ parentDir, fileName }, {
- success_callback: ({ extractedText }) => {
- console.log(extractedText)
- }
+ generateFileTagsAPI(record, {
+ success_callback: updateFileTags
});
- }, [extractText]);
+ }, [updateFileTags, generateFileTagsAPI]);
// init
useEffect(() => {
@@ -452,8 +449,7 @@ export const MetadataViewProvider = ({
const unsubscribeUpdateDetails = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_RECORD_DETAILS, updateRecordDetails);
const unsubscribeUpdateFaceRecognition = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_FACE_RECOGNITION, updateFaceRecognition);
const unsubscribeUpdateDescription = eventBus.subscribe(EVENT_BUS_TYPE.GENERATE_DESCRIPTION, updateRecordDescription);
- const unsubscribeOCR = eventBus.subscribe(EVENT_BUS_TYPE.OCR, ocr);
- const unsubscribeUpdateExtract = eventBus.subscribe(EVENT_BUS_TYPE.EXTRACT_TEXT, updateExtractText);
+ const unsubscribeOCR = eventBus.subscribe(EVENT_BUS_TYPE.OCR, onOCR);
return () => {
if (window.sfMetadataContext) {
@@ -480,7 +476,6 @@ export const MetadataViewProvider = ({
unsubscribeUpdateFaceRecognition();
unsubscribeUpdateDescription();
unsubscribeOCR();
- unsubscribeUpdateExtract();
delayReloadDataTimer.current && clearTimeout(delayReloadDataTimer.current);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -520,8 +515,8 @@ export const MetadataViewProvider = ({
updateRecordDetails,
updateFaceRecognition,
updateRecordDescription,
- updateExtractText,
- ocr,
+ onOCR,
+ generateFileTags,
}}
>
{children}
diff --git a/frontend/src/metadata/utils/column/index.js b/frontend/src/metadata/utils/column/index.js
index 25bd4b7c46..1911fbfe2e 100644
--- a/frontend/src/metadata/utils/column/index.js
+++ b/frontend/src/metadata/utils/column/index.js
@@ -193,8 +193,6 @@ export const getColumnDisplayName = (key, name) => {
return gettext('Document keywords');
case PRIVATE_COLUMN_KEY.FILE_DESCRIPTION:
return gettext('Description');
- case PRIVATE_COLUMN_KEY.OCR:
- return gettext('OCR result');
case PRIVATE_COLUMN_KEY.FILE_EXPIRED:
return gettext('Is expired');
case PRIVATE_COLUMN_KEY.FILE_STATUS:
@@ -264,8 +262,6 @@ export const getNormalizedColumnType = (key, type) => {
return CellType.TEXT;
case PRIVATE_COLUMN_KEY.FILE_DESCRIPTION:
return CellType.LONG_TEXT;
- case PRIVATE_COLUMN_KEY.OCR:
- return CellType.TEXT;
case PRIVATE_COLUMN_KEY.FILE_EXPIRED:
return CellType.CHECKBOX;
case PRIVATE_COLUMN_KEY.FILE_STATUS:
diff --git a/frontend/src/metadata/views/face-recognition/peoples/index.js b/frontend/src/metadata/views/face-recognition/peoples/index.js
index dc60efdf29..3b2855715f 100644
--- a/frontend/src/metadata/views/face-recognition/peoples/index.js
+++ b/frontend/src/metadata/views/face-recognition/peoples/index.js
@@ -63,7 +63,7 @@ const Peoples = ({ peoples, onOpenPeople, onRename }) => {
return () => {};
}, []);
- if (!Array.isArray(peoples) || peoples.length === 0) return ();
+ if (!Array.isArray(peoples) || peoples.length === 0) return ();
return (
diff --git a/frontend/src/metadata/views/kanban/index.js b/frontend/src/metadata/views/kanban/index.js
index 042b5a3305..eb74f47f78 100644
--- a/frontend/src/metadata/views/kanban/index.js
+++ b/frontend/src/metadata/views/kanban/index.js
@@ -23,7 +23,7 @@ const Kanban = () => {
modifyRecordAPI(rowId, updates, oldRowData, originalUpdates, originalOldRowData, false, { success_callback: () => {
success_callback && success_callback();
const eventBus = window.sfMetadataContext.eventBus;
- eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, rowId, updates);
+ eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, { recordId: rowId }, updates);
} });
}, [modifyRecordAPI]);
diff --git a/frontend/src/metadata/views/table/context-menu.js b/frontend/src/metadata/views/table/context-menu.js
index 59a400ebf3..0d2037fffb 100644
--- a/frontend/src/metadata/views/table/context-menu.js
+++ b/frontend/src/metadata/views/table/context-menu.js
@@ -4,7 +4,7 @@ import { useMetadataStatus } from '@/hooks';
import { gettext } from '@/utils/constants';
import { Utils } from '@/utils/utils';
import DeleteFolderDialog from '@/components/dialog/delete-folder-dialog';
-import MoveDirent from '@/components/dialog/move-dirent-dialog';
+import MoveDirentDialog from '@/components/dialog/move-dirent-dialog';
import { Dirent } from '@/models';
import { useMetadataView } from '../../hooks/metadata-view';
import RowUtils from './utils/row-utils';
@@ -12,7 +12,6 @@ import { checkIsDir } from '../../utils/row';
import { getColumnByKey, isNameColumn } from '../../utils/column';
import { EVENT_BUS_TYPE, EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../constants';
import { getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
-import FileTagsDialog from '../../components/dialog/file-tags-dialog';
import ContextMenuComponent from '../../components/context-menu';
import { openInNewTab, openParentFolder } from '../../utils/file';
@@ -33,19 +32,17 @@ const OPERATION = {
FILE_DETAILS: 'file-details',
DETECT_FACES: 'detect-faces',
MOVE: 'move',
- EXTRACT_TEXT: 'extract_text',
};
const { enableSeafileAI } = window.app.config;
const ContextMenu = ({
isGroupView, selectedRange, selectedPosition, recordMetrics, recordGetterByIndex, onClearSelected, onCopySelected,
- getTableContentRect, getTableCanvasContainerRect, deleteRecords, selectNone, updateFileTags, moveRecord, addFolder, updateRecordDetails,
- updateFaceRecognition, updateRecordDescription, ocr, updateExtractText
+ getTableContentRect, getTableCanvasContainerRect, deleteRecords, selectNone, moveRecord, addFolder, updateRecordDetails,
+ updateFaceRecognition, updateRecordDescription, onOCR, generateFileTags
}) => {
const currentRecord = useRef(null);
- const [fileTagsRecord, setFileTagsRecord] = useState(null);
const [deletedFolderPath, setDeletedFolderPath] = useState('');
const [isMoveDialogShow, setMoveDialogShow] = useState(false);
@@ -230,16 +227,12 @@ const ContextMenu = ({
});
}
- if (tagsColumn && isDescribableFile && !isVideo) {
+ if (enableSeafileAI && tagsColumn && isDescribableFile && !isVideo) {
aiOptions.push({ value: OPERATION.FILE_TAGS, label: gettext('Generate file tags'), record: record });
}
- if (enableOCR && isImage) {
- aiOptions.push({ value: OPERATION.OCR, label: gettext('OCR'), record });
- }
-
- if (isImage || isPdf) {
- aiOptions.push({ value: OPERATION.EXTRACT_TEXT, label: gettext('Extract text'), record });
+ if (enableSeafileAI && enableOCR && (isImage || isPdf)) {
+ aiOptions.push({ value: OPERATION.OCR, label: gettext('Extract text'), record });
}
if (aiOptions.length > 0) {
@@ -251,10 +244,6 @@ const ContextMenu = ({
return list;
}, [isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableFile, enableOCR, getAbleDeleteRecords]);
- const toggleFileTagsRecord = useCallback((record = null) => {
- setFileTagsRecord(record);
- }, []);
-
const handleMoveRecord = useCallback((...params) => {
selectNone();
moveRecord && moveRecord(...params);
@@ -290,18 +279,13 @@ const ContextMenu = ({
case OPERATION.FILE_TAGS: {
const { record } = option;
if (!record) break;
- toggleFileTagsRecord(record);
+ generateFileTags(record);
break;
}
case OPERATION.OCR: {
const { record } = option;
if (!record) break;
- ocr(record);
- break;
- }
- case OPERATION.EXTRACT_TEXT: {
- const { record } = option;
- updateExtractText(record)
+ onOCR(record);
break;
}
case OPERATION.DELETE_RECORD: {
@@ -357,7 +341,7 @@ const ContextMenu = ({
break;
}
}
- }, [repoID, onCopySelected, onClearSelected, updateRecordDescription, toggleFileTagsRecord, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateRecordDetails, updateFaceRecognition, toggleMoveDialog, updateExtractText]);
+ }, [repoID, onCopySelected, onClearSelected, updateRecordDescription, generateFileTags, onOCR, deleteRecords, toggleDeleteFolderDialog, selectNone, updateRecordDetails, updateFaceRecognition, toggleMoveDialog]);
useEffect(() => {
const unsubscribeToggleMoveDialog = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, toggleMoveDialog);
@@ -380,9 +364,6 @@ const ContextMenu = ({
boundaryCoordinates={{ top, left, right, bottom }}
onOptionClick={handleOptionClick}
/>
- {fileTagsRecord && (
-
- )}
{deletedFolderPath && (
)}
{isMoveDialogShow && (
- {
updateRecordDetails,
updateFaceRecognition,
updateRecordDescription,
- updateExtractText,
- ocr,
+ onOCR,
+ generateFileTags,
} = useMetadataView();
const containerRef = useRef(null);
@@ -178,6 +178,7 @@ const Table = () => {
modifyColumnWidth={modifyColumnWidth}
modifyColumnOrder={modifyColumnOrder}
updateFileTags={updateFileTags}
+ generateFileTags={generateFileTags}
onGridKeyDown={onHotKey}
onGridKeyUp={onHotKeyUp}
moveRecord={moveRecord}
@@ -186,8 +187,7 @@ const Table = () => {
updateRecordDetails={updateRecordDetails}
updateFaceRecognition={updateFaceRecognition}
updateRecordDescription={updateRecordDescription}
- updateExtractText={updateExtractText}
- ocr={ocr}
+ onOCR={onOCR}
/>
);
diff --git a/frontend/src/metadata/views/table/masks/interaction-masks/index.js b/frontend/src/metadata/views/table/masks/interaction-masks/index.js
index b6a6714cad..0ff2b8e965 100644
--- a/frontend/src/metadata/views/table/masks/interaction-masks/index.js
+++ b/frontend/src/metadata/views/table/masks/interaction-masks/index.js
@@ -1224,7 +1224,7 @@ class InteractionMasks extends React.Component {
onCopySelected: this.onCopySelected,
getTableContentRect: this.props.getTableContentRect,
getTableCanvasContainerRect: this.props.getTableCanvasContainerRect,
- updateFileTags: this.props.updateFileTags,
+ generateFileTags: this.props.generateFileTags,
})}
);
diff --git a/frontend/src/metadata/views/table/table-main/records/body.js b/frontend/src/metadata/views/table/table-main/records/body.js
index e3ded436d6..7f84ef6b5a 100644
--- a/frontend/src/metadata/views/table/table-main/records/body.js
+++ b/frontend/src/metadata/views/table/table-main/records/body.js
@@ -561,6 +561,7 @@ class RecordsBody extends Component {
updateFileTags={this.props.updateFileTags}
deleteRecords={this.props.deleteRecords}
moveRecord={this.props.moveRecord}
+ generateFileTags={this.props.generateFileTags}
/>
{this.renderRecords()}
diff --git a/frontend/src/metadata/views/table/table-main/records/group-body/index.js b/frontend/src/metadata/views/table/table-main/records/group-body/index.js
index 60e17304b6..c8819ff890 100644
--- a/frontend/src/metadata/views/table/table-main/records/group-body/index.js
+++ b/frontend/src/metadata/views/table/table-main/records/group-body/index.js
@@ -915,6 +915,7 @@ class GroupBody extends Component {
modifyColumnData={this.props.modifyColumnData}
getTableCanvasContainerRect={this.props.getTableCanvasContainerRect}
updateFileTags={this.props.updateFileTags}
+ generateFileTags={this.props.generateFileTags}
/>
{this.renderGroups()}
diff --git a/frontend/src/metadata/views/table/table-main/records/index.js b/frontend/src/metadata/views/table/table-main/records/index.js
index 5b7834c96e..a9d8767f6a 100644
--- a/frontend/src/metadata/views/table/table-main/records/index.js
+++ b/frontend/src/metadata/views/table/table-main/records/index.js
@@ -648,8 +648,7 @@ class Records extends Component {
updateRecordDetails={this.props.updateRecordDetails}
updateFaceRecognition={this.props.updateFaceRecognition}
updateRecordDescription={this.props.updateRecordDescription}
- ocr={this.props.ocr}
- updateExtractText={this.props.updateExtractText}
+ onOCR={this.props.onOCR}
/>
),
hasSelectedRecord: this.hasSelectedRecord(),
diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js
index 5a8674259a..44fe54c2e7 100644
--- a/frontend/src/pages/lib-content-view/lib-content-view.js
+++ b/frontend/src/pages/lib-content-view/lib-content-view.js
@@ -23,9 +23,8 @@ import CopyMoveDirentProgressDialog from '../../components/dialog/copy-move-dire
import DeleteFolderDialog from '../../components/dialog/delete-folder-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
import { PRIVATE_FILE_TYPE, DIRENT_DETAIL_SHOW_KEY, TREE_PANEL_STATE_KEY, RECENTLY_USED_LIST_KEY } from '../../constants';
-import { MetadataStatusProvider, DownloadFileProvider } from '../../hooks';
-import { MetadataProvider, CollaboratorsProvider } from '../../metadata/hooks';
-import { TagsProvider } from '../../tag/hooks';
+import { MetadataStatusProvider, DownloadFileProvider, MetadataMiddlewareProvider } from '../../hooks';
+import { MetadataProvider } from '../../metadata/hooks';
import { LIST_MODE, METADATA_MODE, TAGS_MODE } from '../../components/dir-view-mode/constants';
import CurDirPath from '../../components/cur-dir-path';
import DirTool from '../../components/cur-dir-path/dir-tool';
@@ -2360,236 +2359,234 @@ class LibContentView extends React.Component {
-
+
-
-
-
- {this.state.currentRepoInfo.status === 'read-only' &&
-
- {gettext('This library has been set to read-only by admin and cannot be updated.')}
-
- }
-
-
- {isDirentSelected ? (
- currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
-
- ) : (
-
- )
+
+
+ {this.state.currentRepoInfo.status === 'read-only' &&
+
+ {gettext('This library has been set to read-only by admin and cannot be updated.')}
+
+ }
+
+
+ {isDirentSelected ? (
+ currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
+
) : (
-
- )}
-
- {isDesktop &&
-
-
-
- }
-
-
- {this.state.pathExist ?
-
- :
-
{gettext('Folder does not exist.')}
- }
- {!isCustomPermission && this.state.isDirentDetailShow && (
-
)}
+ {isDesktop &&
+
+
+
+ }
+
+
+ {this.state.pathExist ?
+
+ :
+
{gettext('Folder does not exist.')}
+ }
+ {!isCustomPermission && this.state.isDirentDetailShow && (
+
+ )}
- {canUpload && this.state.pathExist && !this.state.isViewFile && ![METADATA_MODE, TAGS_MODE].includes(this.state.currentMode) && (
-
this.uploader = uploader}
- dragAndDrop={true}
- path={this.state.path}
- repoID={this.props.repoID}
- direntList={this.state.direntList}
- onFileUploadSuccess={this.onFileUploadSuccess}
- isCustomPermission={isCustomPermission}
- />
- )}
- {isCopyMoveProgressDialogShow && (
-
- )}
- {isDeleteFolderDialogOpen && (
-
this.uploader = uploader}
+ dragAndDrop={true}
+ path={this.state.path}
repoID={this.props.repoID}
- path={this.state.folderToDelete}
- deleteFolder={this.deleteFolder}
- toggleDialog={this.toggleDeleteFolderDialog}
+ direntList={this.state.direntList}
+ onFileUploadSuccess={this.onFileUploadSuccess}
+ isCustomPermission={isCustomPermission}
/>
)}
-
-
-
-
+
+ {isCopyMoveProgressDialogShow && (
+
+ )}
+ {isDeleteFolderDialogOpen && (
+
+ )}
+
+
+
-
+
diff --git a/frontend/src/tag/views/tag-files/index.js b/frontend/src/tag/views/tag-files/index.js
index 8eaa14d4b3..75cfbdba32 100644
--- a/frontend/src/tag/views/tag-files/index.js
+++ b/frontend/src/tag/views/tag-files/index.js
@@ -5,7 +5,7 @@ import { gettext, username } from '../../../utils/constants';
import EmptyTip from '../../../components/empty-tip';
import toaster from '../../../components/toast';
import ContextMenu from '../../../components/context-menu/context-menu';
-import MoveDirent from '../../../components/dialog/move-dirent-dialog';
+import MoveDirentDialog from '../../../components/dialog/move-dirent-dialog';
import CopyDirent from '../../../components/dialog/copy-dirent-dialog';
import ZipDownloadDialog from '../../../components/dialog/zip-download-dialog';
import ShareDialog from '../../../components/dialog/share-dialog';
@@ -464,7 +464,7 @@ const TagFiles = () => {
getMenuContainerSize={getMenuContainerSize}
/>
{isMoveDialogOpen && (
-
}>
-
-
- {filePerm === 'rw' ? : }
-
-
+
+ {filePerm === 'rw' ? : }
+
diff --git a/seahub/ai/apis.py b/seahub/ai/apis.py
index 90778e898e..52f8f0fc48 100644
--- a/seahub/ai/apis.py
+++ b/seahub/ai/apis.py
@@ -16,7 +16,7 @@ from seahub.api2.authentication import TokenAuthentication, SdocJWTTokenAuthenti
from seahub.utils import get_file_type_and_ext, IMAGE
from seahub.views import check_folder_permission
from seahub.ai.utils import image_caption, translate, writing_assistant, verify_ai_config, generate_summary, \
- generate_file_tags, ocr, extract_text
+ generate_file_tags, ocr
logger = logging.getLogger(__name__)
@@ -226,7 +226,7 @@ class OCR(APIView):
def post(self, request):
if not verify_ai_config():
- return api_error(status.HTTP_400_BAD_REQUEST, 'OCR server not configured')
+ return api_error(status.HTTP_400_BAD_REQUEST, 'AI server not configured')
repo_id = request.data.get('repo_id')
path = request.data.get('path')
@@ -236,18 +236,15 @@ class OCR(APIView):
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
+ file_type, _ = get_file_type_and_ext(os.path.basename(path))
+ if file_type != IMAGE and not path.lower().endswith('.pdf'):
+ return api_error(status.HTTP_400_BAD_REQUEST, 'file type not image or pdf')
+
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
- try:
- record = RepoMetadata.objects.filter(repo_id=repo_id).first()
- except Exception as e:
- logger.error(e)
- error_msg = 'Internal Server Error'
- return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
-
permission = check_folder_permission(request, repo_id, os.path.dirname(path))
if not permission:
error_msg = 'Permission denied.'
@@ -262,20 +259,26 @@ class OCR(APIView):
if not file_id:
return api_error(status.HTTP_404_NOT_FOUND, f"File {path} not found")
+ file_size = get_file_size(repo.store_id, repo.version, file_id)
+ if file_size >> 20 > 5:
+ error_msg = 'File size exceed the limit.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', request.user.username, use_onetime=True)
if not token:
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
params = {
- 'path': path,
- 'download_token': token
+ 'file_name': os.path.basename(path),
+ 'download_token': token,
}
try:
resp = ocr(params)
resp_json = resp.json()
except Exception as e:
+ logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
@@ -348,68 +351,3 @@ class WritingAssistant(APIView):
return Response(resp_json, resp.status_code)
-
-class ExtractText(APIView):
- authentication_classes = (TokenAuthentication, SessionAuthentication)
- permission_classes = (IsAuthenticated,)
- throttle_classes = (UserRateThrottle,)
-
- def post(self, request):
- if not verify_ai_config():
- return api_error(status.HTTP_400_BAD_REQUEST, 'AI server not configured')
-
- repo_id = request.data.get('repo_id')
- path = request.data.get('path')
-
- if not repo_id:
- return api_error(status.HTTP_400_BAD_REQUEST, 'repo_id invalid')
- if not path:
- return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
-
- file_type, _ = get_file_type_and_ext(os.path.basename(path))
- if file_type != IMAGE and not path.lower().endswith('.pdf'):
- return api_error(status.HTTP_400_BAD_REQUEST, 'file type not image or pdf')
-
- repo = seafile_api.get_repo(repo_id)
- if not repo:
- error_msg = 'Library %s not found.' % repo_id
- return api_error(status.HTTP_404_NOT_FOUND, error_msg)
-
- permission = check_folder_permission(request, repo_id, os.path.dirname(path))
- if not permission:
- error_msg = 'Permission denied.'
- return api_error(status.HTTP_403_FORBIDDEN, error_msg)
-
- try:
- file_id = seafile_api.get_file_id_by_path(repo_id, path)
- except SearpcError as e:
- logger.error(e)
- return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
-
- if not file_id:
- return api_error(status.HTTP_404_NOT_FOUND, f"File {path} not found")
-
- file_size = get_file_size(repo.store_id, repo.version, file_id)
- if file_size >> 20 > 5:
- error_msg = 'File size exceed the limit.'
- return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
-
- token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', request.user.username, use_onetime=True)
- if not token:
- error_msg = 'Internal Server Error'
- return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
-
- params = {
- 'file_name': os.path.basename(path),
- 'download_token': token,
- }
-
- try:
- resp = extract_text(params)
- resp_json = resp.json()
- except Exception as e:
- logger.error(e)
- error_msg = 'Internal Server Error'
- return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
-
- return Response(resp_json, resp.status_code)
diff --git a/seahub/ai/urls.py b/seahub/ai/urls.py
new file mode 100644
index 0000000000..ad1b1a85fb
--- /dev/null
+++ b/seahub/ai/urls.py
@@ -0,0 +1,12 @@
+from django.urls import re_path
+from .apis import ImageCaption, GenerateSummary, GenerateFileTags, OCR, Translate, WritingAssistant
+
+urlpatterns = [
+ re_path(r'^image-caption/$', ImageCaption.as_view(), name='api-v2.1-image-caption'),
+ re_path(r'^generate-file-tags/$', GenerateFileTags.as_view(), name='api-v2.1-generate-file-tags'),
+ re_path(r'^generate-summary/$', GenerateSummary.as_view(), name='api-v2.1-generate-summary'),
+ re_path(r'^ocr/$', OCR.as_view(), name='api-v2.1-ocr'),
+ re_path(r'^translate/$', Translate.as_view(), name='api-v2.1-translate'),
+ re_path(r'^writing-assistant/$', WritingAssistant.as_view(), name='api-v2.1-writing-assistant'),
+]
+
diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py
index 6645586245..1b73641ab9 100644
--- a/seahub/repo_metadata/apis.py
+++ b/seahub/repo_metadata/apis.py
@@ -17,8 +17,8 @@ from seahub.views import check_folder_permission
from seahub.repo_metadata.utils import add_init_metadata_task, recognize_faces, gen_unique_id, init_metadata, \
get_unmodifiable_columns, can_read_metadata, init_faces, \
extract_file_details, get_table_by_name, remove_faces_table, FACES_SAVE_PATH, \
- init_tags, init_tag_self_link_columns, remove_tags_table, add_init_face_recognition_task, init_ocr, \
- remove_ocr_column, get_update_record, update_people_cover_photo
+ init_tags, init_tag_self_link_columns, remove_tags_table, add_init_face_recognition_task, \
+ get_update_record, update_people_cover_photo
from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_view_records
from seahub.utils.repo import is_repo_admin
from seaserv import seafile_api
@@ -249,9 +249,6 @@ class MetadataOCRManageView(APIView):
logger.exception(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
- metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
- init_ocr(metadata_server_api)
-
return Response({'success': True})
def delete(self, request, repo_id):
@@ -272,14 +269,6 @@ class MetadataOCRManageView(APIView):
error_msg = f'The repo {repo_id} has disabled the OCR.'
return api_error(status.HTTP_409_CONFLICT, error_msg)
- metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
- try:
- remove_ocr_column(metadata_server_api)
- except Exception as err:
- logger.error(err)
- error_msg = 'Internal Server Error'
- return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
-
try:
record.ocr_enabled = False
record.save()
diff --git a/seahub/repo_metadata/metadata_server_api.py b/seahub/repo_metadata/metadata_server_api.py
index b5b6f78137..5e809b8820 100644
--- a/seahub/repo_metadata/metadata_server_api.py
+++ b/seahub/repo_metadata/metadata_server_api.py
@@ -67,8 +67,6 @@ def list_metadata_view_records(repo_id, user, view, tags_enabled, start=0, limit
column_name = column.get('name')
if column_name == METADATA_TABLE.columns.face_vectors.name:
continue
- elif column_name == METADATA_TABLE.columns.ocr.name:
- continue
column_name_str = '`%s`, ' % column_name
query_fields_str += column_name_str
query_fields_str = query_fields_str.strip(', ')
diff --git a/seahub/repo_metadata/utils.py b/seahub/repo_metadata/utils.py
index aed44b8d66..1a1f258b50 100644
--- a/seahub/repo_metadata/utils.py
+++ b/seahub/repo_metadata/utils.py
@@ -273,32 +273,6 @@ def remove_tags_table(metadata_server_api):
metadata_server_api.delete_column(table['id'], column['key'], True)
-# ocr
-def init_ocr(metadata_server_api):
- from seafevents.repo_metadata.constants import METADATA_TABLE
-
- remove_ocr_column(metadata_server_api)
-
- # init ocr column
- columns = [
- METADATA_TABLE.columns.ocr.to_dict(),
- ]
- metadata_server_api.add_columns(METADATA_TABLE.id, columns)
-
-
-def remove_ocr_column(metadata_server_api):
- from seafevents.repo_metadata.constants import METADATA_TABLE
- metadata = metadata_server_api.get_metadata()
-
- tables = metadata.get('tables', [])
- for table in tables:
- if table['name'] == METADATA_TABLE.name:
- columns = table.get('columns', [])
- for column in columns:
- if column['key'] == METADATA_TABLE.columns.ocr.key:
- metadata_server_api.delete_column(table['id'], METADATA_TABLE.columns.ocr.key, True)
-
-
def get_file_download_token(repo_id, file_id, username):
return seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', username, use_onetime=True)
diff --git a/seahub/urls.py b/seahub/urls.py
index caf88f08f2..b70fe6eebb 100644
--- a/seahub/urls.py
+++ b/seahub/urls.py
@@ -2,8 +2,6 @@
from django.urls import include, path, re_path
from django.views.generic import TemplateView
-from seahub.ai.apis import ImageCaption, GenerateSummary, GenerateFileTags, OCR, Translate, WritingAssistant, \
- ExtractText
from seahub.api2.endpoints.file_comments import FileCommentsView, FileCommentView, FileCommentRepliesView, \
FileCommentReplyView
from seahub.api2.endpoints.share_link_auth import ShareLinkUserAuthView, ShareLinkEmailAuthView
@@ -1080,13 +1078,8 @@ if getattr(settings, 'ENABLE_METADATA_MANAGEMENT', False):
re_path(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/metadata/', include('seahub.repo_metadata.urls')),
]
-# ai API
-urlpatterns += [
- re_path(r'^api/v2.1/ai/image-caption/$', ImageCaption.as_view(), name='api-v2.1-image-caption'),
- re_path(r'^api/v2.1/ai/generate-file-tags/$', GenerateFileTags.as_view(), name='api-v2.1-generate-file-tags'),
- re_path(r'^api/v2.1/ai/generate-summary/$', GenerateSummary.as_view(), name='api-v2.1-generate-summary'),
- re_path(r'^api/v2.1/ai/ocr/$', OCR.as_view(), name='api-v2.1-ocr'),
- re_path(r'^api/v2.1/ai/translate/$', Translate.as_view(), name='api-v2.1-translate'),
- re_path(r'^api/v2.1/ai/writing-assistant/$', WritingAssistant.as_view(), name='api-v2.1-writing-assistant'),
- re_path(r'^api/v2.1/ai/extract-text/$', ExtractText.as_view(), name='api-v2.1-extract-text'),
-]
+ if getattr(settings, 'ENABLE_SEAFILE_AI', False):
+ urlpatterns += [
+ re_path(r'^api/v2.1/ai/', include('seahub.ai.urls')),
+ ]
+