mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-01 23:38:37 +00:00
feat: file details ai (#7251)
Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
parent
b139c235f7
commit
9b8b7c9324
21
frontend/src/assets/icons/ai.svg
Normal file
21
frontend/src/assets/icons/ai.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#999999;}
|
||||
</style>
|
||||
<title>ai</title>
|
||||
<g id="ai">
|
||||
<g id="ai-assistant">
|
||||
<path id="形状" class="st0" d="M16.1,0C24.9,0,32,6.8,32,15s-7.1,15-15.9,15c-0.1,0-0.3,0-0.6,0c-0.4,0-3.7,0.2-4.9,0.6
|
||||
c-1,0.2-2.8,0.9-3.9,1.2C6.6,31.9,6.2,32,6,32c-0.5,0-0.9-0.4-1-1c-0.2-1.1-0.2-3,0.4-4.7C2.1,23.5,0,19.6,0,15
|
||||
C0.1,6.8,7.2,0,16.1,0z M16,3C8.8,3,3,8.6,3,15.4c0,3.5,1.7,6.6,4.5,9.1l1.2,1.1c-0.4,0.8-0.7,1.3-0.8,1.7
|
||||
c-0.2,0.5-0.3,1.3-0.2,1.7c0.8-0.4,2-1,2.9-1.2c1.7-0.4,4.2-0.6,5.4-0.6c5.7,0,13-5,13-11.8S23.2,3,16,3z M13.1,6.9
|
||||
C10.3,6.9,8,9.1,8,11.8v10c0,0.1,0.1,0.3,0.3,0.3h2.8V11.9c0-1,0.9-1.9,1.9-1.9s1.9,0.9,1.9,1.9v2.3H12c-0.1,0-0.3,0-0.4,0.1
|
||||
c-0.1,0.1-0.1,0.3-0.1,0.4v1.9c0,0.4,0.3,0.6,0.6,0.6H15v4.9h2.8c0.1,0,0.3-0.1,0.3-0.3v-10C18.1,9.1,15.9,6.9,13.1,6.9L13.1,6.9z
|
||||
M22.4,11h-1.8c-0.4,0-0.6,0.2-0.6,0.6v9.8c0,0.4,0.2,0.6,0.6,0.6h1.8c0.4,0,0.6-0.2,0.6-0.6v-9.8C23,11.2,22.8,11,22.4,11z
|
||||
M21.5,10c0.8,0,1.5-0.7,1.5-1.5S22.3,7,21.5,7S20,7.7,20,8.5S20.7,10,21.5,10z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -2,13 +2,13 @@ import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import Lightbox from '@seafile/react-image-lightbox';
|
||||
import { useMetadataOperations } from '../../hooks/metadata-operation';
|
||||
import { useMetadataAIOperations } from '../../hooks/metadata-ai-operation';
|
||||
import { SYSTEM_FOLDERS } from '../../constants';
|
||||
|
||||
import '@seafile/react-image-lightbox/style.css';
|
||||
|
||||
const ImageDialog = ({ enableRotate: oldEnableRotate, imageItems, imageIndex, closeImagePopup, moveToPrevImage, moveToNextImage, onDeleteImage, onRotateImage }) => {
|
||||
const { onOCR } = useMetadataOperations();
|
||||
const { enableOCR, enableMetadata, canModify, onOCR: onOCRAPI, OCRSuccessCallBack } = useMetadataAIOperations();
|
||||
|
||||
const downloadImage = useCallback((url) => {
|
||||
location.href = url;
|
||||
@ -33,6 +33,10 @@ const ImageDialog = ({ enableRotate: oldEnableRotate, imageItems, imageIndex, cl
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
return (
|
||||
<Lightbox
|
||||
@ -57,7 +61,7 @@ const ImageDialog = ({ enableRotate: oldEnableRotate, imageItems, imageIndex, cl
|
||||
onViewOriginal={onViewOriginal}
|
||||
viewOriginalImageLabel={gettext('View original image')}
|
||||
onRotateImage={(onRotateImage && enableRotate) ? (angle) => onRotateImage(imageIndex, angle) : null}
|
||||
onOCR={onOCR && !isSystemFolder ? () => onOCR(mainImg.parentDir, mainImg.name) : null}
|
||||
onOCR={onOCR}
|
||||
OCRLabel={gettext('OCR')}
|
||||
/>
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ import DirDetails from './dir-details';
|
||||
import FileDetails from './file-details';
|
||||
import ObjectUtils from '../../../metadata/utils/object-utils';
|
||||
import { MetadataDetailsProvider } from '../../../metadata/hooks';
|
||||
import Settings from '../../../metadata/components/metadata-details/settings';
|
||||
import { Settings, AI } from '../../../metadata/components/metadata-details';
|
||||
import { getDirentPath } from './utils';
|
||||
|
||||
import './index.css';
|
||||
@ -129,6 +129,7 @@ class DirentDetails extends React.Component {
|
||||
>
|
||||
<Detail>
|
||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={this.props.onClose} >
|
||||
<AI />
|
||||
<Settings />
|
||||
</Header>
|
||||
<Body>
|
||||
|
@ -8,7 +8,7 @@ import { Header, Body } from '../detail';
|
||||
import FileDetails from './file-details';
|
||||
import { MetadataContext } from '../../../metadata';
|
||||
import { MetadataDetailsProvider } from '../../../metadata/hooks';
|
||||
import Settings from '../../../metadata/components/metadata-details/settings';
|
||||
import { AI, Settings } from '../../../metadata/components/metadata-details';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@ -54,6 +54,7 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width =
|
||||
style={{ width }}
|
||||
>
|
||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={onClose} component={headerComponent} >
|
||||
<AI />
|
||||
<Settings />
|
||||
</Header>
|
||||
<Body>
|
||||
|
109
frontend/src/hooks/metadata-ai-operation.js
Normal file
109
frontend/src/hooks/metadata-ai-operation.js
Normal file
@ -0,0 +1,109 @@
|
||||
import React, { useContext, useCallback, useMemo } 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';
|
||||
|
||||
// This hook provides content related to metadata ai operation
|
||||
const MetadataAIOperationsContext = React.createContext(null);
|
||||
|
||||
export const MetadataAIOperationsProvider = ({
|
||||
repoID,
|
||||
enableMetadata = false,
|
||||
enableOCR = false,
|
||||
enableTags = false,
|
||||
tagsLang,
|
||||
repoInfo,
|
||||
children
|
||||
}) => {
|
||||
const permission = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw', [repoInfo]);
|
||||
const canModify = useMemo(() => permission === 'rw', [permission]);
|
||||
|
||||
const OCRSuccessCallBack = useCallback(({ parentDir, fileName, ocrResult } = {}) => {
|
||||
toaster.success(gettext('Successfully OCR'));
|
||||
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 onOCR = useCallback(({ parentDir, fileName }, { success_callback, fail_callback } = {}) => {
|
||||
const filePath = Utils.joinPath(parentDir, fileName);
|
||||
metadataAPI.ocr(repoID, filePath).then(res => {
|
||||
const ocrResult = res.data.ocr_result;
|
||||
const validResult = Array.isArray(ocrResult) && ocrResult.length > 0 ? JSON.stringify(ocrResult) : null;
|
||||
success_callback && success_callback({ parentDir, fileName, ocrResult: validResult });
|
||||
}).catch(error => {
|
||||
const errorMessage = gettext('OCR failed');
|
||||
toaster.danger(errorMessage);
|
||||
fail_callback && fail_callback();
|
||||
});
|
||||
}, [repoID]);
|
||||
|
||||
const generateDescription = useCallback(({ parentDir, fileName }, { success_callback, fail_callback } = {}) => {
|
||||
const filePath = Utils.joinPath(parentDir, fileName);
|
||||
const isImage = Utils.imageCheck(fileName);
|
||||
let APIName = '';
|
||||
if (Utils.isDescriptionSupportedFile(fileName)) {
|
||||
APIName = isImage ? 'imageCaption' : 'generateDescription';
|
||||
}
|
||||
if (!APIName) return;
|
||||
|
||||
metadataAPI[APIName](repoID, filePath, lang).then(res => {
|
||||
const description = res?.data?.summary || res.data.desc || '';
|
||||
success_callback && success_callback({ parentDir, fileName, description });
|
||||
}).catch(error => {
|
||||
const errorMessage = isImage ? gettext('Failed to generate image description') : gettext('Failed to generate description');
|
||||
toaster.danger(errorMessage);
|
||||
fail_callback && fail_callback();
|
||||
});
|
||||
}, [repoID]);
|
||||
|
||||
const extractFilesDetails = useCallback((objIds, { success_callback, fail_callback } = {}) => {
|
||||
metadataAPI.extractFileDetails(repoID, objIds).then(res => {
|
||||
const details = res?.data?.details || [];
|
||||
success_callback && success_callback({ details });
|
||||
}).catch(error => {
|
||||
const errorMessage = gettext('Failed to extract file details');
|
||||
toaster.danger(errorMessage);
|
||||
fail_callback && fail_callback();
|
||||
});
|
||||
}, [repoID]);
|
||||
|
||||
const extractFileDetails = useCallback((objId, { success_callback, fail_callback } = {}) => {
|
||||
extractFilesDetails([objId], {
|
||||
success_callback: ({ details }) => {
|
||||
success_callback && success_callback({ detail: details[0] });
|
||||
},
|
||||
fail_callback
|
||||
});
|
||||
}, [extractFilesDetails]);
|
||||
|
||||
return (
|
||||
<MetadataAIOperationsContext.Provider value={{
|
||||
enableMetadata,
|
||||
enableOCR,
|
||||
enableTags,
|
||||
tagsLang,
|
||||
canModify,
|
||||
onOCR,
|
||||
OCRSuccessCallBack,
|
||||
generateDescription,
|
||||
extractFilesDetails,
|
||||
extractFileDetails,
|
||||
}}>
|
||||
{children}
|
||||
</MetadataAIOperationsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMetadataAIOperations = () => {
|
||||
const context = useContext(MetadataAIOperationsContext);
|
||||
if (!context) {
|
||||
throw new Error('\'MetadataAIOperationsContext\' is null');
|
||||
}
|
||||
return context;
|
||||
};
|
@ -1,54 +0,0 @@
|
||||
import React, { useContext, useCallback, useMemo } 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 } from '../utils/constants';
|
||||
|
||||
// This hook provides content related to metadata record operation
|
||||
const MetadataOperationsContext = React.createContext(null);
|
||||
|
||||
export const MetadataOperationsProvider = ({ repoID, enableMetadata, enableOCR, repoInfo, children }) => {
|
||||
const permission = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw', [repoInfo]);
|
||||
const canModify = useMemo(() => permission === 'rw', [permission]);
|
||||
|
||||
const onOCR = useCallback((parentDir, fileName) => {
|
||||
const filePath = Utils.joinPath(parentDir, fileName);
|
||||
metadataAPI.ocr(repoID, filePath).then(res => {
|
||||
const ocrResult = res.data.ocr_result;
|
||||
const validResult = Array.isArray(ocrResult) && ocrResult.length > 0 ? JSON.stringify(ocrResult) : null;
|
||||
toaster.success(gettext('Successfully OCR'));
|
||||
if (validResult) {
|
||||
const update = { [PRIVATE_COLUMN_KEY.OCR]: validResult };
|
||||
metadataAPI.modifyRecord(repoID, { parentDir, fileName }, update).then(res => {
|
||||
const eventBus = window?.sfMetadataContext?.eventBus;
|
||||
if (eventBus) {
|
||||
eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, { parentDir, fileName }, update);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
const errorMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMessage);
|
||||
});
|
||||
}, [repoID]);
|
||||
|
||||
let value = {};
|
||||
if (canModify && enableMetadata && enableOCR) {
|
||||
value['onOCR'] = onOCR;
|
||||
}
|
||||
|
||||
return (
|
||||
<MetadataOperationsContext.Provider value={value}>
|
||||
{children}
|
||||
</MetadataOperationsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMetadataOperations = () => {
|
||||
const context = useContext(MetadataOperationsContext);
|
||||
if (!context) {
|
||||
throw new Error('\'MetadataOperationsContext\' is null');
|
||||
}
|
||||
return context;
|
||||
};
|
@ -2,7 +2,7 @@ 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 { MetadataOperationsProvider } from './metadata-operation';
|
||||
import { MetadataAIOperationsProvider } from './metadata-ai-operation';
|
||||
|
||||
// This hook provides content related to seahub interaction, such as whether to enable extended attributes
|
||||
const MetadataStatusContext = React.createContext(null);
|
||||
@ -119,14 +119,16 @@ export const MetadataStatusProvider = ({ repoID, currentRepoInfo, hideMetadataVi
|
||||
}}
|
||||
>
|
||||
{!isLoading && (
|
||||
<MetadataOperationsProvider
|
||||
<MetadataAIOperationsProvider
|
||||
repoID={repoID}
|
||||
enableMetadata={enableMetadata}
|
||||
enableOCR={enableOCR}
|
||||
enableTags={enableTags}
|
||||
tagsLang={tagsLang}
|
||||
repoInfo={currentRepoInfo}
|
||||
>
|
||||
{children}
|
||||
</MetadataOperationsProvider>
|
||||
</MetadataAIOperationsProvider>
|
||||
)}
|
||||
</MetadataStatusContext.Provider>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LongTextFormatter } from '@seafile/sf-metadata-ui-component';
|
||||
import Editor from '../../cell-editors/long-text-editor';
|
||||
@ -10,6 +10,8 @@ const LongTextEditor = ({ field, value: oldValue, onChange }) => {
|
||||
const [value, setValue] = useState(oldValue);
|
||||
const [showEditor, setShowEditor] = useState(false);
|
||||
|
||||
const valueRef = useRef(null);
|
||||
|
||||
const openEditor = useCallback(() => {
|
||||
setShowEditor(true);
|
||||
}, []);
|
||||
@ -23,6 +25,13 @@ const LongTextEditor = ({ field, value: oldValue, onChange }) => {
|
||||
setShowEditor(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (showEditor) return;
|
||||
if (valueRef.current === oldValue) return;
|
||||
setValue(oldValue);
|
||||
valueRef.current = oldValue;
|
||||
}, [showEditor, oldValue]);
|
||||
|
||||
const isEmpty = !value || !value.trim();
|
||||
|
||||
return (
|
||||
|
@ -0,0 +1,3 @@
|
||||
.sf-metadata-ai-dropdown-menu .dropdown-menu {
|
||||
left: -8px !important;
|
||||
}
|
180
frontend/src/metadata/components/metadata-details/ai/index.js
Normal file
180
frontend/src/metadata/components/metadata-details/ai/index.js
Normal file
@ -0,0 +1,180 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||
import { ModalPortal } from '@seafile/sf-metadata-ui-component';
|
||||
import Icon from '../../../../components/icon';
|
||||
import { useMetadataDetails } from '../../../hooks';
|
||||
import { useMetadataStatus } from '../../../../hooks';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { getFileNameFromRecord, getFileObjIdFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../../utils/cell';
|
||||
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';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const OPERATION = {
|
||||
GENERATE_DESCRIPTION: 'generate-description',
|
||||
OCR: 'ocr',
|
||||
FILE_TAGS: 'file-tags',
|
||||
FILE_DETAIL: 'file-detail',
|
||||
};
|
||||
|
||||
const AI = () => {
|
||||
const [isMenuShow, setMenuShow] = useState(false);
|
||||
const [isFileTagsDialogShow, setFileTagsDialogShow] = useState(false);
|
||||
|
||||
const { enableMetadata, enableTags, enableOCR } = useMetadataStatus();
|
||||
const { canModifyRecord, columns, record, onChange, onLocalRecordChange, updateFileTags } = useMetadataDetails();
|
||||
const { onOCR, generateDescription, extractFileDetails } = useMetadataAIOperations();
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!canModifyRecord) return [];
|
||||
if (!record) return [];
|
||||
if (checkIsDir(record)) return [];
|
||||
const descriptionColumn = getColumnByKey(columns, PRIVATE_COLUMN_KEY.FILE_DESCRIPTION);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
const isImage = Utils.imageCheck(fileName);
|
||||
const isVideo = Utils.videoCheck(fileName);
|
||||
const isDescribableDoc = Utils.isDescriptionSupportedFile(fileName);
|
||||
let list = [];
|
||||
|
||||
if (descriptionColumn && isDescribableDoc) {
|
||||
list.push({
|
||||
value: OPERATION.GENERATE_DESCRIPTION,
|
||||
label: isImage ? gettext('Generate image description') : gettext('Generate description'),
|
||||
record
|
||||
});
|
||||
}
|
||||
|
||||
if (enableOCR && isImage) {
|
||||
list.push({ value: OPERATION.OCR, label: gettext('OCR'), record });
|
||||
}
|
||||
|
||||
if (isImage || isVideo) {
|
||||
list.push({ value: OPERATION.FILE_DETAIL, label: gettext('Extract file detail'), record });
|
||||
}
|
||||
|
||||
if (enableTags && isDescribableDoc && !isVideo) {
|
||||
list.push({ value: OPERATION.FILE_TAGS, label: gettext('Generate file tags'), record });
|
||||
}
|
||||
return list;
|
||||
}, [enableOCR, enableTags, canModifyRecord, columns, record]);
|
||||
|
||||
const onToggle = useCallback((event) => {
|
||||
event && event.preventDefault();
|
||||
event && event.stopPropagation();
|
||||
setMenuShow(!isMenuShow);
|
||||
}, [isMenuShow]);
|
||||
|
||||
const toggleFileTagsDialog = useCallback(() => {
|
||||
setFileTagsDialogShow(!isFileTagsDialogShow);
|
||||
}, [isFileTagsDialogShow]);
|
||||
|
||||
const handelOperation = useCallback((op) => {
|
||||
const { value: opType, record } = op;
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
const objId = getFileObjIdFromRecord(record);
|
||||
|
||||
switch (opType) {
|
||||
case OPERATION.GENERATE_DESCRIPTION: {
|
||||
generateDescription({ parentDir, fileName }, {
|
||||
success_callback: ({ description }) => {
|
||||
if (!description) return;
|
||||
onChange && onChange(PRIVATE_COLUMN_KEY.FILE_DESCRIPTION, description);
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case OPERATION.OCR: {
|
||||
onOCR({ parentDir, fileName }, {
|
||||
success_callback: ({ ocrResult }) => {
|
||||
if (!ocrResult) return;
|
||||
onChange && onChange(PRIVATE_COLUMN_KEY.OCR, JSON.stringify(ocrResult));
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case OPERATION.FILE_TAGS: {
|
||||
setFileTagsDialogShow(true);
|
||||
break;
|
||||
}
|
||||
case OPERATION.FILE_DETAIL: {
|
||||
extractFileDetails(objId, {
|
||||
success_callback: ({ detail }) => {
|
||||
if (!detail) return;
|
||||
const captureColumn = getColumnByKey(columns, PRIVATE_COLUMN_KEY.CAPTURE_TIME);
|
||||
if (captureColumn) {
|
||||
const value = detail[PRIVATE_COLUMN_KEY.CAPTURE_TIME];
|
||||
value && onChange && onChange(PRIVATE_COLUMN_KEY.CAPTURE_TIME, value);
|
||||
}
|
||||
const fileDetails = detail[PRIVATE_COLUMN_KEY.FILE_DETAILS];
|
||||
const location = detail[PRIVATE_COLUMN_KEY.LOCATION];
|
||||
let update = {};
|
||||
if (fileDetails) {
|
||||
update[PRIVATE_COLUMN_KEY.FILE_DETAILS] = fileDetails;
|
||||
}
|
||||
if (location) {
|
||||
update[PRIVATE_COLUMN_KEY.LOCATION] = location;
|
||||
}
|
||||
Object.keys(update).length > 0 && onLocalRecordChange(recordId, update);
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
setMenuShow(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [columns, generateDescription, onOCR, extractFileDetails, onChange, onLocalRecordChange]);
|
||||
|
||||
const renderDropdown = useCallback(() => {
|
||||
if (!enableMetadata) return null;
|
||||
if (!canModifyRecord) return null;
|
||||
if (!record) return null;
|
||||
if (options.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Dropdown className="sf-metadata-dropdown-menu" isOpen={isMenuShow} toggle={onToggle}>
|
||||
<DropdownToggle
|
||||
tag="span"
|
||||
role="button"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded={isMenuShow}
|
||||
title={gettext('AI')}
|
||||
aria-label={gettext('AI')}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="detail-control mr-2">
|
||||
<Icon symbol="ai" className="detail-control-close" />
|
||||
</div>
|
||||
</DropdownToggle>
|
||||
{isMenuShow && (
|
||||
<ModalPortal>
|
||||
<div className="sf-metadata-ai-dropdown-menu large">
|
||||
<DropdownMenu right={true}>
|
||||
{options.map(op => (<DropdownItem key={op.value} onClick={() => handelOperation(op)}>{op.label}</DropdownItem>))}
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</ModalPortal>
|
||||
)}
|
||||
</Dropdown>
|
||||
);
|
||||
}, [isMenuShow, enableMetadata, canModifyRecord, record, options, onToggle, handelOperation]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderDropdown()}
|
||||
{isFileTagsDialogShow && (
|
||||
<FileTagsDialog record={record} onToggle={toggleFileTagsDialog} onSubmit={updateFileTags} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AI;
|
@ -9,6 +9,8 @@ import { PRIVATE_COLUMN_KEY } from '../../constants';
|
||||
import Location from './location';
|
||||
import { useMetadataDetails } from '../../hooks';
|
||||
import { checkIsDir } from '../../utils/row';
|
||||
import AI from './ai';
|
||||
import Settings from './settings';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@ -64,3 +66,7 @@ const MetadataDetails = () => {
|
||||
};
|
||||
|
||||
export default MetadataDetails;
|
||||
export {
|
||||
AI,
|
||||
Settings,
|
||||
};
|
||||
|
@ -10,7 +10,7 @@ const Settings = () => {
|
||||
const [isShowSetter, setShowSetter] = useState(false);
|
||||
|
||||
const { enableMetadata } = useMetadataStatus();
|
||||
const { modifyColumnOrder, modifyHiddenColumns, columns, canModifyDetails } = useMetadataDetails();
|
||||
const { modifyColumnOrder, modifyHiddenColumns, record, columns, canModifyDetails } = useMetadataDetails();
|
||||
const hiddenColumns = useMemo(() => columns.filter(c => !c.shown).map(c => c.key), [columns]);
|
||||
|
||||
const onSetterToggle = useCallback(() => {
|
||||
@ -20,6 +20,7 @@ const Settings = () => {
|
||||
|
||||
if (!enableMetadata) return null;
|
||||
if (!canModifyDetails) return null;
|
||||
if (!record) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -49,7 +49,7 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
|
||||
return [...exitColumnsOrder, ...newColumns];
|
||||
}, [originColumns, detailsSettings]);
|
||||
|
||||
const localRecordChanged = useCallback((recordId, updates) => {
|
||||
const onLocalRecordChange = useCallback((recordId, updates) => {
|
||||
if (getRecordIdFromRecord(record) !== recordId) return;
|
||||
const newRecord = { ...record, ...updates };
|
||||
setRecord(newRecord);
|
||||
@ -180,11 +180,11 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
|
||||
useEffect(() => {
|
||||
const eventBus = window?.sfMetadataContext?.eventBus;
|
||||
if (!eventBus) return;
|
||||
const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, localRecordChanged);
|
||||
const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, onLocalRecordChange);
|
||||
return () => {
|
||||
unsubscribeLocalRecordChanged();
|
||||
};
|
||||
}, [localRecordChanged]);
|
||||
}, [onLocalRecordChange]);
|
||||
|
||||
return (
|
||||
<MetadataDetailsContext.Provider
|
||||
@ -195,6 +195,7 @@ export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, dirent
|
||||
record,
|
||||
columns,
|
||||
onChange,
|
||||
onLocalRecordChange,
|
||||
modifyColumnData,
|
||||
updateFileTags,
|
||||
modifyHiddenColumns,
|
||||
|
@ -16,6 +16,7 @@ import { openInNewTab, openParentFolder } from '../../../utils/file';
|
||||
import DeleteFolderDialog from '../../../../components/dialog/delete-folder-dialog';
|
||||
import MoveDirent from '../../../../components/dialog/move-dirent-dialog';
|
||||
import { Dirent } from '../../../../models';
|
||||
import { useMetadataAIOperations } from '../../../../hooks/metadata-ai-operation';
|
||||
|
||||
const OPERATION = {
|
||||
CLEAR_SELECTED: 'clear-selected',
|
||||
@ -24,7 +25,7 @@ const OPERATION = {
|
||||
OPEN_IN_NEW_TAB: 'open-new-tab',
|
||||
GENERATE_DESCRIPTION: 'generate-description',
|
||||
OCR: 'ocr',
|
||||
IMAGE_CAPTION: 'image-caption',
|
||||
IMAGE_DESCRIPTION: 'image-description',
|
||||
FILE_TAGS: 'file-tags',
|
||||
DELETE_RECORD: 'delete-record',
|
||||
DELETE_RECORDS: 'delete-records',
|
||||
@ -49,6 +50,7 @@ const ContextMenu = ({
|
||||
|
||||
const { metadata } = useMetadataView();
|
||||
const { enableOCR } = useMetadataStatus();
|
||||
const { onOCR, generateDescription, extractFilesDetails } = useMetadataAIOperations();
|
||||
|
||||
const repoID = window.sfMetadataStore.repoId;
|
||||
|
||||
@ -56,7 +58,7 @@ const ContextMenu = ({
|
||||
return window.sfMetadataContext.canModifyRow(row);
|
||||
};
|
||||
|
||||
const checkIsDescribableDoc = useCallback((record) => {
|
||||
const checkIsDescribableFile = useCallback((record) => {
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
return checkCanModifyRow(record) && Utils.isDescriptionSupportedFile(fileName);
|
||||
}, []);
|
||||
@ -142,6 +144,10 @@ const ContextMenu = ({
|
||||
list.push({ value: OPERATION.DELETE_RECORDS, label: gettext('Delete'), records: ableDeleteRecords });
|
||||
}
|
||||
const imageOrVideoRecords = records.filter(record => {
|
||||
const isFolder = checkIsDir(record);
|
||||
if (isFolder) return false;
|
||||
const canModifyRow = checkCanModifyRow(record);
|
||||
if (!canModifyRow) return false;
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
return Utils.imageCheck(fileName) || Utils.videoCheck(fileName);
|
||||
});
|
||||
@ -165,24 +171,29 @@ const ContextMenu = ({
|
||||
list.push({ value: OPERATION.OPEN_PARENT_FOLDER, label: gettext('Open parent folder'), record });
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
|
||||
if (descriptionColumn) {
|
||||
if (checkIsDescribableDoc(record)) {
|
||||
list.push({ value: OPERATION.GENERATE_DESCRIPTION, label: gettext('Generate description'), record });
|
||||
} else if (canModifyRow && Utils.imageCheck(fileName)) {
|
||||
list.push({ value: OPERATION.IMAGE_CAPTION, label: gettext('Generate image description'), record });
|
||||
if (!isFolder && canModifyRow) {
|
||||
const isDescribableFile = checkIsDescribableFile(record);
|
||||
const isImage = Utils.imageCheck(fileName);
|
||||
const isVideo = Utils.videoCheck(fileName);
|
||||
if (descriptionColumn && isDescribableFile) {
|
||||
list.push({
|
||||
value: OPERATION.GENERATE_DESCRIPTION,
|
||||
label: Utils.imageCheck(fileName) ? gettext('Generate image description') : gettext('Generate description'),
|
||||
record
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (enableOCR && canModifyRow && Utils.imageCheck(fileName)) {
|
||||
list.push({ value: OPERATION.OCR, label: gettext('OCR'), record });
|
||||
}
|
||||
if (enableOCR && isImage) {
|
||||
list.push({ value: OPERATION.OCR, label: gettext('OCR'), record });
|
||||
}
|
||||
|
||||
if (canModifyRow && (Utils.imageCheck(fileName) || Utils.videoCheck(fileName))) {
|
||||
list.push({ value: OPERATION.FILE_DETAIL, label: gettext('Extract file detail'), record: record });
|
||||
}
|
||||
if (isImage || isVideo) {
|
||||
list.push({ value: OPERATION.FILE_DETAIL, label: gettext('Extract file detail'), record: record });
|
||||
}
|
||||
|
||||
if (tagsColumn && canModifyRow && (Utils.imageCheck(fileName) || checkIsDescribableDoc(record))) {
|
||||
list.push({ value: OPERATION.FILE_TAGS, label: gettext('Generate file tags'), record: record });
|
||||
if (tagsColumn && isDescribableFile && !isVideo) {
|
||||
list.push({ value: OPERATION.FILE_TAGS, label: gettext('Generate file tags'), record: record });
|
||||
}
|
||||
}
|
||||
|
||||
// handle delete folder/file
|
||||
@ -199,7 +210,7 @@ const ContextMenu = ({
|
||||
}
|
||||
|
||||
return list;
|
||||
}, [visible, isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableDoc, enableOCR, getAbleDeleteRecords]);
|
||||
}, [visible, isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableFile, enableOCR, getAbleDeleteRecords]);
|
||||
|
||||
const handleHide = useCallback((event) => {
|
||||
if (!menuRef.current && visible) {
|
||||
@ -212,83 +223,46 @@ const ContextMenu = ({
|
||||
}
|
||||
}, [menuRef, visible]);
|
||||
|
||||
const generateDescription = useCallback((record) => {
|
||||
const descriptionColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
||||
let path = '';
|
||||
let idOldRecordData = {};
|
||||
let idOriginalOldRecordData = {};
|
||||
const handelGenerateDescription = useCallback((record) => {
|
||||
if (!checkCanModifyRow(record)) return;
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
if (Utils.isDescriptionSupportedFile(fileName) && checkCanModifyRow(record)) {
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
path = Utils.joinPath(parentDir, fileName);
|
||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [descriptionColumnKey]: record[descriptionColumnKey] };
|
||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [descriptionColumnKey]: record[descriptionColumnKey] };
|
||||
}
|
||||
if (path === '') return;
|
||||
window.sfMetadataContext.generateDescription(path).then(res => {
|
||||
const description = res.data.summary;
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
idRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}).catch(error => {
|
||||
const errorMessage = gettext('Failed to generate description');
|
||||
toaster.danger(errorMessage);
|
||||
});
|
||||
}, [updateRecords]);
|
||||
if (!fileName || !parentDir) return;
|
||||
const checkIsDescribableFile = Utils.isDescriptionSupportedFile(fileName);
|
||||
if (!checkIsDescribableFile) return;
|
||||
|
||||
const imageCaption = useCallback((record) => {
|
||||
const summaryColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
||||
let path = '';
|
||||
let idOldRecordData = {};
|
||||
let idOriginalOldRecordData = {};
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
if (Utils.imageCheck(fileName) && checkCanModifyRow(record)) {
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
path = Utils.joinPath(parentDir, fileName);
|
||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
||||
}
|
||||
if (path === '') return;
|
||||
window.sfMetadataContext.imageCaption(path).then(res => {
|
||||
const desc = res.data.desc;
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
idRecordUpdates[updateRecordId] = { [summaryColumnKey]: desc };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [summaryColumnKey]: desc };
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}).catch(error => {
|
||||
const errorMessage = gettext('Failed to generate image description');
|
||||
toaster.danger(errorMessage);
|
||||
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] } };
|
||||
generateDescription({ parentDir, fileName }, {
|
||||
success_callback: ({ description }) => {
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
idRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}
|
||||
});
|
||||
}, [updateRecords]);
|
||||
}, [updateRecords, generateDescription]);
|
||||
|
||||
const toggleFileTagsRecord = useCallback((record = null) => {
|
||||
setFileTagsRecord(record);
|
||||
}, []);
|
||||
|
||||
const ocr = useCallback((record) => {
|
||||
const ocrResultColumnKey = PRIVATE_COLUMN_KEY.OCR;
|
||||
let path = '';
|
||||
let idOldRecordData = {};
|
||||
let idOriginalOldRecordData = {};
|
||||
if (!checkCanModifyRow(record)) return;
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
if (Utils.imageCheck(fileName) && checkCanModifyRow(record)) {
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
path = Utils.joinPath(parentDir, fileName);
|
||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [ocrResultColumnKey]: record[ocrResultColumnKey] };
|
||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [ocrResultColumnKey]: record[ocrResultColumnKey] };
|
||||
}
|
||||
if (path === '') return;
|
||||
window.sfMetadataContext.ocr(path).then(res => {
|
||||
const ocrResult = res.data.ocr_result;
|
||||
const validResult = Array.isArray(ocrResult) && ocrResult.length > 0 ? JSON.stringify(ocrResult) : null;
|
||||
if (validResult) {
|
||||
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;
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
@ -296,12 +270,9 @@ const ContextMenu = ({
|
||||
idRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}
|
||||
}).catch(error => {
|
||||
const errorMessage = gettext('OCR failed');
|
||||
toaster.danger(errorMessage);
|
||||
},
|
||||
});
|
||||
}, [updateRecords]);
|
||||
}, [updateRecords, onOCR]);
|
||||
|
||||
const updateFileDetails = useCallback((records) => {
|
||||
const recordObjIds = records.map(record => getFileObjIdFromRecord(record));
|
||||
@ -311,10 +282,10 @@ const ContextMenu = ({
|
||||
}
|
||||
|
||||
const recordIds = records.map(record => getRecordIdFromRecord(record));
|
||||
window.sfMetadataContext.extractFileDetails(recordObjIds).then(res => {
|
||||
const captureColumn = getColumnByKey(metadata.columns, PRIVATE_COLUMN_KEY.CAPTURE_TIME);
|
||||
|
||||
if (captureColumn) {
|
||||
extractFilesDetails(recordObjIds, {
|
||||
success_callback: ({ details }) => {
|
||||
const captureColumn = getColumnByKey(metadata.columns, PRIVATE_COLUMN_KEY.CAPTURE_TIME);
|
||||
if (!captureColumn) return;
|
||||
let idOldRecordData = {};
|
||||
let idOriginalOldRecordData = {};
|
||||
const captureColumnKey = PRIVATE_COLUMN_KEY.CAPTURE_TIME;
|
||||
@ -324,18 +295,15 @@ const ContextMenu = ({
|
||||
});
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
res.data.details.forEach(detail => {
|
||||
details.forEach(detail => {
|
||||
const updateRecordId = detail[PRIVATE_COLUMN_KEY.ID];
|
||||
idRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
|
||||
});
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}
|
||||
}).catch(error => {
|
||||
const errorMessage = gettext('Failed to extract file details');
|
||||
toaster.danger(errorMessage);
|
||||
});
|
||||
}, [metadata, updateRecords]);
|
||||
}, [metadata, extractFilesDetails, updateRecords]);
|
||||
|
||||
const handleOptionClick = useCallback((event, option) => {
|
||||
event.stopPropagation();
|
||||
@ -363,13 +331,7 @@ const ContextMenu = ({
|
||||
case OPERATION.GENERATE_DESCRIPTION: {
|
||||
const { record } = option;
|
||||
if (!record) break;
|
||||
generateDescription(record);
|
||||
break;
|
||||
}
|
||||
case OPERATION.IMAGE_CAPTION: {
|
||||
const { record } = option;
|
||||
if (!record) break;
|
||||
imageCaption(record);
|
||||
handelGenerateDescription(record);
|
||||
break;
|
||||
}
|
||||
case OPERATION.FILE_TAGS: {
|
||||
@ -433,7 +395,7 @@ const ContextMenu = ({
|
||||
}
|
||||
}
|
||||
setVisible(false);
|
||||
}, [repoID, onCopySelected, onClearSelected, generateDescription, imageCaption, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord, toggleMoveDialog]);
|
||||
}, [repoID, onCopySelected, onClearSelected, handelGenerateDescription, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord, toggleMoveDialog]);
|
||||
|
||||
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
||||
let menuStyles = {
|
||||
|
@ -15,13 +15,15 @@
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.sdoc-content-right-panel .detail-header .seafile-multicolor-icon-set-up {
|
||||
.sdoc-content-right-panel .detail-header .seafile-multicolor-icon-set-up,
|
||||
.sdoc-content-right-panel .detail-header .seafile-multicolor-icon-ai {
|
||||
fill: #999;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sdoc-content-right-panel .detail-header .detail-control:hover .seafile-multicolor-icon-set-up {
|
||||
.sdoc-content-right-panel .detail-header .detail-control:hover .seafile-multicolor-icon-set-up,
|
||||
.sdoc-content-right-panel .detail-header .detail-control:hover .seafile-multicolor-icon-ai {
|
||||
fill: #5a5a5a;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import CopyMoveDirentProgressDialog from './components/dialog/copy-move-dirent-p
|
||||
import RepoInfoBar from './components/repo-info-bar';
|
||||
import RepoTag from './models/repo-tag';
|
||||
import { GRID_MODE, LIST_MODE } from './components/dir-view-mode/constants';
|
||||
import { MetadataOperationsProvider } from './hooks/metadata-operation';
|
||||
import { MetadataAIOperationsProvider } from './hooks/metadata-ai-operation';
|
||||
|
||||
import './css/shared-dir-view.css';
|
||||
import './css/grid-view.css';
|
||||
@ -441,7 +441,7 @@ class SharedDirView extends React.Component {
|
||||
const isDesktop = Utils.isDesktop();
|
||||
const modeBaseClass = 'btn btn-secondary btn-icon sf-view-mode-btn';
|
||||
return (
|
||||
<MetadataOperationsProvider repoID={repoID} enableMetadata={false} enableOCR={false} repoInfo={{ permission: 'r' }} >
|
||||
<MetadataAIOperationsProvider repoID={repoID} enableMetadata={false} enableOCR={false} repoInfo={{ permission: 'r' }} >
|
||||
<div className="h-100 d-flex flex-column">
|
||||
<div className="top-header d-flex justify-content-between">
|
||||
<a href={siteRoot}>
|
||||
@ -581,7 +581,7 @@ class SharedDirView extends React.Component {
|
||||
/>
|
||||
</ModalPortal>
|
||||
}
|
||||
</MetadataOperationsProvider>
|
||||
</MetadataAIOperationsProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1055,7 +1055,12 @@ export const Utils = {
|
||||
},
|
||||
|
||||
isDescriptionSupportedFile: function (filePath) {
|
||||
return Utils.isSdocFile(filePath) || Utils.isMarkdownFile(filePath) || Utils.pdfCheck(filePath) || Utils.isDocxFile(filePath) || Utils.isPptxFile(filePath);
|
||||
return Utils.isSdocFile(filePath) ||
|
||||
Utils.isMarkdownFile(filePath) ||
|
||||
Utils.pdfCheck(filePath) ||
|
||||
Utils.isDocxFile(filePath) ||
|
||||
Utils.isPptxFile(filePath) ||
|
||||
Utils.imageCheck(filePath);
|
||||
},
|
||||
|
||||
isFileMetadata: function (type) {
|
||||
|
Loading…
Reference in New Issue
Block a user