Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pdf): add gen dual layer pdf sup #7343

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions frontend/src/hooks/metadata-ai-operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,38 @@ export const MetadataAIOperationsProvider = ({
});
}, [repoID]);

const genDualLayerPDF = useCallback(({ parentDir, fileName }, { success_callback, fail_callback } = {}) => {
const filePath = Utils.joinPath(parentDir, fileName);
const inProgressToaster = toaster.notifyInProgress(gettext('Making PDF searchable by AI...'), { duration: 5 });
metadataAPI.genDualLayerPDF(repoID, filePath).then(res => {
if (res.data.task_status === 'processing') {
toaster.notifyInProgress(gettext('The task has been started, perhaps by another user'), { duration: 5 });
} else if (res.data.task_status === 'already_ocr') {
const userConfirmed = window.confirm(gettext('The text has already been OCR processed. Do you want to proceed with OCR again?'));
if (userConfirmed) {
metadataAPI.genDualLayerPDF(repoID, filePath, { force: true }).then(res => {
if (res.data.task_status === 'processing') {
toaster.notifyInProgress(gettext('The task has been started, perhaps by another user'), { duration: 5 });
} else {
success_callback && success_callback();
}
}).catch(() => {
const errorMessage = gettext('Failed to make PDF searchable');
toaster.danger(errorMessage);
fail_callback && fail_callback();
});
}
} else {
success_callback && success_callback();
}
}).catch(() => {
inProgressToaster.close();
const errorMessage = gettext('Failed to make PDF searchable');
toaster.danger(errorMessage);
fail_callback && fail_callback();
});
}, [repoID]);

const extractFilesDetails = useCallback((objIds, { success_callback, fail_callback } = {}) => {
const inProgressToaster = toaster.notifyInProgress(gettext('Extracting file details by AI...'), { duration: null });
metadataAPI.extractFileDetails(repoID, objIds).then(res => {
Expand Down Expand Up @@ -102,6 +134,7 @@ export const MetadataAIOperationsProvider = ({
onOCR,
OCRSuccessCallBack,
generateDescription,
genDualLayerPDF,
extractFilesDetails,
extractFileDetails,
}}>
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/metadata/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ class MetadataManagerAPI {
return this.req.post(url, params);
};

genDualLayerPDF = (repoID, filePath, options = {}) => {
const url = this.server + '/api/v2.1/ai/pdf/generate-text-layer/';
const params = {
path: filePath,
repo_id: repoID,
...options,
};
return this.req.post(url, params);
};

imageCaption = (repoID, filePath, lang) => {
const url = this.server + '/api/v2.1/ai/image-caption/';
const params = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const OPERATION = {
OCR: 'ocr',
FILE_TAGS: 'file-tags',
FILE_DETAIL: 'file-detail',
GEN_DUAL_LAYER_PDF: 'gen-dual-layer-pdf',
};

const AI = () => {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/metadata/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ class Context {
return this.metadataAPI.generateDescription(repoID, filePath);
};

genDualLayerPDF = (filePath, options = {}) => {
const repoID = this.settings['repoID'];
return this.metadataAPI.genDualLayerPDF(repoID, filePath, ...options);
};

imageCaption = (filePath) => {
const repoID = this.settings['repoID'];
const lang = this.settings['lang'];
Expand Down
28 changes: 26 additions & 2 deletions frontend/src/metadata/views/table/context-menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const OPERATION = {
FILE_DETAIL: 'file-detail',
FILE_DETAILS: 'file-details',
MOVE: 'move',
GEN_DUAL_LAYER_PDF: 'gen-dual-layer-pdf',
};

const ContextMenu = ({
Expand All @@ -48,7 +49,7 @@ const ContextMenu = ({

const { metadata } = useMetadataView();
const { enableOCR } = useMetadataStatus();
const { onOCR, generateDescription, extractFilesDetails } = useMetadataAIOperations();
const { onOCR, generateDescription, extractFilesDetails, genDualLayerPDF } = useMetadataAIOperations();

const repoID = window.sfMetadataStore.repoId;

Expand Down Expand Up @@ -172,6 +173,7 @@ const ContextMenu = ({
const isDescribableFile = checkIsDescribableFile(record);
const isImage = Utils.imageCheck(fileName);
const isVideo = Utils.videoCheck(fileName);
const isPDF = Utils.pdfCheck(fileName);
if (descriptionColumn && isDescribableFile) {
list.push({
value: OPERATION.GENERATE_DESCRIPTION,
Expand All @@ -180,6 +182,14 @@ const ContextMenu = ({
});
}

if (isPDF) {
list.push({
value: OPERATION.GEN_DUAL_LAYER_PDF,
label: gettext('Make the PDF searchable'),
record
});
}

if (enableOCR && isImage) {
list.push({ value: OPERATION.OCR, label: gettext('OCR'), record });
}
Expand Down Expand Up @@ -233,6 +243,14 @@ const ContextMenu = ({
});
}, [updateRecords, generateDescription]);

const handleGenerateDualLayerPDF = useCallback((record) => {
const parentDir = getParentDirFromRecord(record);
const fileName = getFileNameFromRecord(record);
if (!fileName || !parentDir) return;
if (!Utils.pdfCheck(fileName)) return;
genDualLayerPDF({ parentDir, fileName });
}, [genDualLayerPDF]);

const toggleFileTagsRecord = useCallback((record = null) => {
setFileTagsRecord(record);
}, []);
Expand Down Expand Up @@ -318,6 +336,12 @@ const ContextMenu = ({
handelGenerateDescription(record);
break;
}
case OPERATION.GEN_DUAL_LAYER_PDF: {
const { record } = option;
if (!record) break;
handleGenerateDualLayerPDF(record);
break;
}
case OPERATION.FILE_TAGS: {
const { record } = option;
if (!record) break;
Expand Down Expand Up @@ -378,7 +402,7 @@ const ContextMenu = ({
break;
}
}
}, [repoID, onCopySelected, onClearSelected, handelGenerateDescription, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord, toggleMoveDialog]);
}, [repoID, onCopySelected, onClearSelected, handelGenerateDescription, handleGenerateDualLayerPDF, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord, toggleMoveDialog]);

const currentRecordId = getRecordIdFromRecord(currentRecord.current);
const fileName = getFileNameFromRecord(currentRecord.current);
Expand Down
73 changes: 72 additions & 1 deletion seahub/ai/apis.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os.path
import json

from pysearpc import SearpcError
from seahub.repo_metadata.models import RepoMetadata
Expand All @@ -15,7 +16,8 @@
from seahub.api2.authentication import TokenAuthentication, SdocJWTTokenAuthentication
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
from seahub.ai.utils import image_caption, translate, writing_assistant, \
verify_ai_config, generate_summary, generate_file_tags, ocr, generate_dual_layer_pdf

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -343,3 +345,72 @@ def post(self, request):
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

return Response(resp_json, resp.status_code)


class GenDualLayerPDF(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')
force = request.data.get('force')
username = request.user.username

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')

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")

download_token = seafile_api.get_fileserver_access_token(
repo_id, file_id, 'download', username, use_onetime=True
)
parent_dir = os.path.dirname(path)
obj_id = json.dumps({'parent_dir': parent_dir})
upload_token = seafile_api.get_fileserver_access_token(
repo_id, obj_id, 'upload-link', username, use_onetime=True
)
if not (download_token and upload_token):
error_msg = 'Internal Server Error, '
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

params = {
'path': path,
'download_token': download_token,
'upload_token': upload_token,
'repo_id': repo_id,
'force': force,
}
try:
resp = generate_dual_layer_pdf(params)
resp_json = resp.json()
except Exception as e:
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

return Response(resp_json, resp.status_code)
7 changes: 7 additions & 0 deletions seahub/ai/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ def generate_summary(params):
return resp


def generate_dual_layer_pdf(params):
headers = gen_headers()
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/pdf/generate-text-layer/')
resp = requests.post(url, json=params, headers=headers)
return resp


def generate_file_tags(params):
headers = gen_headers()
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/generate-file-tags/')
Expand Down
4 changes: 3 additions & 1 deletion seahub/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
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
from seahub.ai.apis import ImageCaption, GenerateSummary, GenerateFileTags, \
OCR, Translate, WritingAssistant, GenDualLayerPDF
from seahub.api2.endpoints.share_link_auth import ShareLinkUserAuthView, ShareLinkEmailAuthView
from seahub.api2.endpoints.internal_api import InternalUserListView, InternalCheckShareLinkAccess, \
InternalCheckFileOperationAccess
Expand Down Expand Up @@ -1060,4 +1061,5 @@
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/pdf/generate-text-layer/$', GenDualLayerPDF.as_view(), name='api-v2.1-pdf-generate-text-layer'),
]
Loading