mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-17 07:41:26 +00:00
generate doc tags (#7182)
* generate doc tags * update --------- Co-authored-by: zheng.shen <zheng.shen@seafile.com>
This commit is contained in:
@@ -228,8 +228,8 @@ class MetadataManagerAPI {
|
|||||||
return this.req.post(url, params);
|
return this.req.post(url, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
imageTags = (repoID, filePath) => {
|
generateFileTags = (repoID, filePath) => {
|
||||||
const url = this.server + '/api/v2.1/ai/image-tags/';
|
const url = this.server + '/api/v2.1/ai/generate-file-tags/';
|
||||||
const params = {
|
const params = {
|
||||||
path: filePath,
|
path: filePath,
|
||||||
repo_id: repoID,
|
repo_id: repoID,
|
||||||
|
@@ -15,11 +15,11 @@ import { useTags } from '../../../../tag/hooks';
|
|||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const ImageTagsDialog = ({ record, onToggle, onSubmit }) => {
|
const FileTagsDialog = ({ record, onToggle, onSubmit }) => {
|
||||||
|
|
||||||
const [isLoading, setLoading] = useState(true);
|
const [isLoading, setLoading] = useState(true);
|
||||||
const [isSubmitting, setSubmitting] = useState(false);
|
const [isSubmitting, setSubmitting] = useState(false);
|
||||||
const [imageTags, setImageTags] = useState([]);
|
const [fileTags, setFileTags] = useState([]);
|
||||||
const [selectedTags, setSelectedTags] = useState([]);
|
const [selectedTags, setSelectedTags] = useState([]);
|
||||||
|
|
||||||
const fileName = useMemo(() => getFileNameFromRecord(record), [record]);
|
const fileName = useMemo(() => getFileNameFromRecord(record), [record]);
|
||||||
@@ -28,7 +28,7 @@ const ImageTagsDialog = ({ record, onToggle, onSubmit }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let path = '';
|
let path = '';
|
||||||
if (Utils.imageCheck(fileName) && window.sfMetadataContext.canModifyRow(record)) {
|
if (window.sfMetadataContext.canModifyRow(record)) {
|
||||||
const parentDir = getParentDirFromRecord(record);
|
const parentDir = getParentDirFromRecord(record);
|
||||||
path = Utils.joinPath(parentDir, fileName);
|
path = Utils.joinPath(parentDir, fileName);
|
||||||
}
|
}
|
||||||
@@ -36,12 +36,12 @@ const ImageTagsDialog = ({ record, onToggle, onSubmit }) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.sfMetadataContext.imageTags(path).then(res => {
|
window.sfMetadataContext.generateFileTags(path).then(res => {
|
||||||
const tags = res.data.tags;
|
const tags = res.data.tags || [];
|
||||||
setImageTags(tags);
|
setFileTags(tags);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
const errorMessage = gettext('Failed to generate image tags');
|
const errorMessage = gettext('Failed to generate file tags');
|
||||||
toaster.danger(errorMessage);
|
toaster.danger(errorMessage);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
@@ -124,9 +124,9 @@ const ImageTagsDialog = ({ record, onToggle, onSubmit }) => {
|
|||||||
<CenteredLoading />
|
<CenteredLoading />
|
||||||
) : (
|
) : (
|
||||||
<div className="auto-image-tags-container">
|
<div className="auto-image-tags-container">
|
||||||
{imageTags.length > 0 ? (
|
{fileTags.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{imageTags.map((tagName, index) => {
|
{fileTags.map((tagName, index) => {
|
||||||
const isSelected = selectedTags.includes(tagName);
|
const isSelected = selectedTags.includes(tagName);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -153,10 +153,10 @@ const ImageTagsDialog = ({ record, onToggle, onSubmit }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ImageTagsDialog.propTypes = {
|
FileTagsDialog.propTypes = {
|
||||||
record: PropTypes.object,
|
record: PropTypes.object,
|
||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ImageTagsDialog;
|
export default FileTagsDialog;
|
@@ -230,9 +230,9 @@ class Context {
|
|||||||
return this.metadataAPI.imageCaption(repoID, filePath, lang);
|
return this.metadataAPI.imageCaption(repoID, filePath, lang);
|
||||||
};
|
};
|
||||||
|
|
||||||
imageTags = (filePath) => {
|
generateFileTags = (filePath) => {
|
||||||
const repoID = this.settings['repoID'];
|
const repoID = this.settings['repoID'];
|
||||||
return this.metadataAPI.imageTags(repoID, filePath);
|
return this.metadataAPI.generateFileTags(repoID, filePath);
|
||||||
};
|
};
|
||||||
|
|
||||||
extractFileDetails = (objIds) => {
|
extractFileDetails = (objIds) => {
|
||||||
|
@@ -10,7 +10,7 @@ import { EVENT_BUS_TYPE, EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, PRIVATE_COLU
|
|||||||
import { getFileNameFromRecord, getParentDirFromRecord, getFileObjIdFromRecord,
|
import { getFileNameFromRecord, getParentDirFromRecord, getFileObjIdFromRecord,
|
||||||
getRecordIdFromRecord,
|
getRecordIdFromRecord,
|
||||||
} from '../../../utils/cell';
|
} from '../../../utils/cell';
|
||||||
import ImageTagsDialog from '../../../components/dialog/image-tags-dialog';
|
import FileTagsDialog from '../../../components/dialog/file-tags-dialog';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ const OPERATION = {
|
|||||||
OPEN_IN_NEW_TAB: 'open-new-tab',
|
OPEN_IN_NEW_TAB: 'open-new-tab',
|
||||||
GENERATE_DESCRIPTION: 'generate-description',
|
GENERATE_DESCRIPTION: 'generate-description',
|
||||||
IMAGE_CAPTION: 'image-caption',
|
IMAGE_CAPTION: 'image-caption',
|
||||||
IMAGE_TAGS: 'image-tags',
|
FILE_TAGS: 'file-tags',
|
||||||
DELETE_RECORD: 'delete-record',
|
DELETE_RECORD: 'delete-record',
|
||||||
DELETE_RECORDS: 'delete-records',
|
DELETE_RECORDS: 'delete-records',
|
||||||
RENAME_FILE: 'rename-file',
|
RENAME_FILE: 'rename-file',
|
||||||
@@ -37,7 +37,7 @@ const ContextMenu = (props) => {
|
|||||||
const menuRef = useRef(null);
|
const menuRef = useRef(null);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [position, setPosition] = useState({ top: 0, left: 0 });
|
const [position, setPosition] = useState({ top: 0, left: 0 });
|
||||||
const [imageTagsRecord, setImageTagsRecord] = useState(null);
|
const [fileTagsRecord, setFileTagsRecord] = useState(null);
|
||||||
|
|
||||||
const { metadata } = useMetadataView();
|
const { metadata } = useMetadataView();
|
||||||
|
|
||||||
@@ -143,8 +143,8 @@ const ContextMenu = (props) => {
|
|||||||
list.push({ value: OPERATION.FILE_DETAIL, label: gettext('Extract file detail'), record: record });
|
list.push({ value: OPERATION.FILE_DETAIL, label: gettext('Extract file detail'), record: record });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagsColumn && canModifyRow && Utils.imageCheck(fileName)) {
|
if (tagsColumn && canModifyRow && (Utils.imageCheck(fileName) || checkIsDescribableDoc(record))) {
|
||||||
list.push({ value: OPERATION.IMAGE_TAGS, label: gettext('Generate image tags'), record: record });
|
list.push({ value: OPERATION.FILE_TAGS, label: gettext('Generate file tags'), record: record });
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle delete folder/file
|
// handle delete folder/file
|
||||||
@@ -252,8 +252,8 @@ const ContextMenu = (props) => {
|
|||||||
});
|
});
|
||||||
}, [updateRecords]);
|
}, [updateRecords]);
|
||||||
|
|
||||||
const toggleImageTagsRecord = useCallback((record = null) => {
|
const toggleFileTagsRecord = useCallback((record = null) => {
|
||||||
setImageTagsRecord(record);
|
setFileTagsRecord(record);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateFileDetails = useCallback((records) => {
|
const updateFileDetails = useCallback((records) => {
|
||||||
@@ -325,10 +325,10 @@ const ContextMenu = (props) => {
|
|||||||
imageCaption(record);
|
imageCaption(record);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.IMAGE_TAGS: {
|
case OPERATION.FILE_TAGS: {
|
||||||
const { record } = option;
|
const { record } = option;
|
||||||
if (!record) break;
|
if (!record) break;
|
||||||
toggleImageTagsRecord(record);
|
toggleFileTagsRecord(record);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OPERATION.DELETE_RECORD: {
|
case OPERATION.DELETE_RECORD: {
|
||||||
@@ -374,7 +374,7 @@ const ContextMenu = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateDescription, imageCaption, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleImageTagsRecord]);
|
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateDescription, imageCaption, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord]);
|
||||||
|
|
||||||
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
||||||
let menuStyles = {
|
let menuStyles = {
|
||||||
@@ -461,8 +461,8 @@ const ContextMenu = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{renderMenu()}
|
{renderMenu()}
|
||||||
{imageTagsRecord && (
|
{fileTagsRecord && (
|
||||||
<ImageTagsDialog record={imageTagsRecord} onToggle={toggleImageTagsRecord} onSubmit={updateFileTags} />
|
<FileTagsDialog record={fileTagsRecord} onToggle={toggleFileTagsRecord} onSubmit={updateFileTags} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ from seahub.api2.throttling import UserRateThrottle
|
|||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.utils import get_file_type_and_ext, IMAGE
|
from seahub.utils import get_file_type_and_ext, IMAGE
|
||||||
from seahub.views import check_folder_permission
|
from seahub.views import check_folder_permission
|
||||||
from seahub.ai.utils import image_caption, verify_ai_config, generate_summary, image_tags
|
from seahub.ai.utils import image_caption, verify_ai_config, generate_summary, generate_file_tags
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ class GenerateSummary(APIView):
|
|||||||
return Response(resp_json, resp.status_code)
|
return Response(resp_json, resp.status_code)
|
||||||
|
|
||||||
|
|
||||||
class ImageTags(APIView):
|
class GenerateFileTags(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
throttle_classes = (UserRateThrottle,)
|
throttle_classes = (UserRateThrottle,)
|
||||||
@@ -162,13 +162,6 @@ class ImageTags(APIView):
|
|||||||
error_msg = 'Library %s not found.' % repo_id
|
error_msg = 'Library %s not found.' % repo_id
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
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))
|
permission = check_folder_permission(request, repo_id, os.path.dirname(path))
|
||||||
if not permission:
|
if not permission:
|
||||||
error_msg = 'Permission denied.'
|
error_msg = 'Permission denied.'
|
||||||
@@ -191,11 +184,32 @@ class ImageTags(APIView):
|
|||||||
params = {
|
params = {
|
||||||
'path': path,
|
'path': path,
|
||||||
'download_token': token,
|
'download_token': token,
|
||||||
'lang': record.tags_lang if record and record.tags_enabled else None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file_type, _ = get_file_type_and_ext(os.path.basename(path))
|
||||||
|
if file_type == IMAGE:
|
||||||
|
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)
|
||||||
|
|
||||||
|
params['file_type'] = 'image'
|
||||||
|
params['lang'] = record.tags_lang if record and record.tags_enabled else None
|
||||||
|
else:
|
||||||
|
from seahub.repo_metadata.metadata_server_api import MetadataServerAPI
|
||||||
|
from seafevents.repo_metadata.constants import TAGS_TABLE
|
||||||
|
metadata_server_api = MetadataServerAPI(repo_id, request.user.username)
|
||||||
|
|
||||||
|
sql = f'SELECT `{TAGS_TABLE.columns.name.name}` FROM `{TAGS_TABLE.name}`'
|
||||||
|
query_result = metadata_server_api.query_rows(sql).get('results', [])
|
||||||
|
|
||||||
|
params['file_type'] = 'doc'
|
||||||
|
params['candidate_tags'] = [item[TAGS_TABLE.columns.name.name].strip() for item in query_result]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = image_tags(params)
|
resp = generate_file_tags(params)
|
||||||
resp_json = resp.json()
|
resp_json = resp.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
|
@@ -35,8 +35,8 @@ def generate_summary(params):
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def image_tags(params):
|
def generate_file_tags(params):
|
||||||
headers = gen_headers()
|
headers = gen_headers()
|
||||||
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/image-tags/')
|
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/generate-file-tags/')
|
||||||
resp = requests.post(url, json=params, headers=headers, timeout=30)
|
resp = requests.post(url, json=params, headers=headers, timeout=30)
|
||||||
return resp
|
return resp
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from seahub.ai.apis import ImageCaption, GenerateSummary, ImageTags
|
from seahub.ai.apis import ImageCaption, GenerateSummary, GenerateFileTags
|
||||||
from seahub.api2.endpoints.share_link_auth import ShareLinkUserAuthView, ShareLinkEmailAuthView
|
from seahub.api2.endpoints.share_link_auth import ShareLinkUserAuthView, ShareLinkEmailAuthView
|
||||||
from seahub.api2.endpoints.internal_api import InternalUserListView, InternalCheckShareLinkAccess, \
|
from seahub.api2.endpoints.internal_api import InternalUserListView, InternalCheckShareLinkAccess, \
|
||||||
InternalCheckFileOperationAccess
|
InternalCheckFileOperationAccess
|
||||||
@@ -1046,6 +1046,6 @@ if getattr(settings, 'ENABLE_METADATA_MANAGEMENT', False):
|
|||||||
# ai API
|
# ai API
|
||||||
urlpatterns += [
|
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/image-caption/$', ImageCaption.as_view(), name='api-v2.1-image-caption'),
|
||||||
re_path(r'^api/v2.1/ai/image-tags/$', ImageTags.as_view(), name='api-v2.1-image-tags'),
|
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/generate-summary/$', GenerateSummary.as_view(), name='api-v2.1-generate-summary'),
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user