mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-09 02:47:51 +00:00
image_caption (#6657)
* image_caption * update * update * update * update * update --------- Co-authored-by: zheng.shen <zheng.shen@seafile.com>
This commit is contained in:
parent
1e5b5565e8
commit
d26c6d119c
@ -194,10 +194,21 @@ class MetadataManagerAPI {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ai
|
// ai
|
||||||
generateSummary = (repoID, filePaths) => {
|
generateSummary = (repoID, filePath) => {
|
||||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/ai/summarize-documents/';
|
const url = this.server + '/api/v2.1/ai/generate-summary/';
|
||||||
const params = {
|
const params = {
|
||||||
file_paths_list: filePaths,
|
path: filePath,
|
||||||
|
repo_id: repoID,
|
||||||
|
};
|
||||||
|
return this.req.post(url, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
imageCaption = (repoID, filePath, lang) => {
|
||||||
|
const url = this.server + '/api/v2.1/ai/image-caption/';
|
||||||
|
const params = {
|
||||||
|
path: filePath,
|
||||||
|
repo_id: repoID,
|
||||||
|
lang: lang,
|
||||||
};
|
};
|
||||||
return this.req.post(url, params);
|
return this.req.post(url, params);
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ const OPERATION = {
|
|||||||
OPEN_PARENT_FOLDER: 'open-parent-folder',
|
OPEN_PARENT_FOLDER: 'open-parent-folder',
|
||||||
OPEN_IN_NEW_TAB: 'open-new-tab',
|
OPEN_IN_NEW_TAB: 'open-new-tab',
|
||||||
GENERATE_SUMMARY: 'generate-summary',
|
GENERATE_SUMMARY: 'generate-summary',
|
||||||
|
IMAGE_CAPTION: 'image-caption',
|
||||||
};
|
};
|
||||||
|
|
||||||
const ContextMenu = ({
|
const ContextMenu = ({
|
||||||
@ -90,6 +91,8 @@ const ContextMenu = ({
|
|||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
||||||
if (Utils.isSdocFile(fileName) && canModifyRow(record)) {
|
if (Utils.isSdocFile(fileName) && canModifyRow(record)) {
|
||||||
list.push({ value: OPERATION.GENERATE_SUMMARY, label: gettext('Generate summary') });
|
list.push({ value: OPERATION.GENERATE_SUMMARY, label: gettext('Generate summary') });
|
||||||
|
} else if (Utils.imageCheck(fileName) && canModifyRow(record)) {
|
||||||
|
list.push({ value: OPERATION.IMAGE_CAPTION, label: gettext('Generate image description') });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,63 +137,65 @@ const ContextMenu = ({
|
|||||||
|
|
||||||
const generateSummary = useCallback(() => {
|
const generateSummary = useCallback(() => {
|
||||||
const canModifyRow = window.sfMetadataContext.canModifyRow;
|
const canModifyRow = window.sfMetadataContext.canModifyRow;
|
||||||
const selectedRecords = Object.keys(recordMetrics.idSelectedRecordMap);
|
|
||||||
const summaryColumnKey = PRIVATE_COLUMN_KEY.FILE_SUMMARY;
|
const summaryColumnKey = PRIVATE_COLUMN_KEY.FILE_SUMMARY;
|
||||||
let paths = [];
|
let path = '';
|
||||||
let idOldRecordData = {};
|
let idOldRecordData = {};
|
||||||
let idOriginalOldRecordData = {};
|
let idOriginalOldRecordData = {};
|
||||||
if (selectedRange) {
|
const { groupRecordIndex, rowIdx } = selectedPosition;
|
||||||
const { topLeft, bottomRight } = selectedRange;
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
||||||
for (let i = topLeft.rowIdx; i <= bottomRight.rowIdx; i++) {
|
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex: topLeft.groupRecordIndex, recordIndex: i });
|
if (Utils.isSdocFile(fileName) && canModifyRow(record)) {
|
||||||
if (!canModifyRow(record)) continue;
|
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
path = Utils.joinPath(parentDir, fileName);
|
||||||
if (!Utils.isSdocFile(fileName)) continue;
|
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
||||||
paths.push(Utils.joinPath(parentDir, fileName));
|
|
||||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
|
||||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
|
||||||
}
|
|
||||||
} else if (selectedRecords.length > 0) {
|
|
||||||
selectedRecords.forEach(recordId => {
|
|
||||||
const record = metadata.id_row_map[recordId];
|
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
|
||||||
if (Utils.isSdocFile(fileName) && canModifyRow(record)) {
|
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
|
||||||
paths.push(Utils.joinPath(parentDir, fileName));
|
|
||||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
|
||||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (selectedPosition) {
|
|
||||||
const { groupRecordIndex, rowIdx } = selectedPosition;
|
|
||||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
|
||||||
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
|
||||||
if (Utils.isSdocFile(fileName) && canModifyRow(record)) {
|
|
||||||
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
|
||||||
paths.push(Utils.joinPath(parentDir, fileName));
|
|
||||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
|
||||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [summaryColumnKey]: record[summaryColumnKey] };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (paths.length === 0) return;
|
if (path === '') return;
|
||||||
window.sfMetadataContext.generateSummary(paths).then(res => {
|
window.sfMetadataContext.generateSummary(path).then(res => {
|
||||||
const updatedRecords = res.data.rows;
|
const summary = res.data.summary;
|
||||||
let recordIds = [];
|
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||||
|
const recordIds = [updateRecordId];
|
||||||
let idRecordUpdates = {};
|
let idRecordUpdates = {};
|
||||||
let idOriginalRecordUpdates = {};
|
let idOriginalRecordUpdates = {};
|
||||||
updatedRecords.forEach(updatedRecord => {
|
idRecordUpdates[updateRecordId] = { [summaryColumnKey]: summary };
|
||||||
const { _id: updateRecordId, _summary } = updatedRecord;
|
idOriginalRecordUpdates[updateRecordId] = { [summaryColumnKey]: summary };
|
||||||
recordIds.push(updateRecordId);
|
|
||||||
idRecordUpdates[updateRecordId] = { [summaryColumnKey]: _summary };
|
|
||||||
idOriginalRecordUpdates[updateRecordId] = { [summaryColumnKey]: _summary };
|
|
||||||
});
|
|
||||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
const errorMessage = gettext('Failed to generate summary');
|
const errorMessage = gettext('Failed to generate summary');
|
||||||
toaster.danger(errorMessage);
|
toaster.danger(errorMessage);
|
||||||
});
|
});
|
||||||
}, [isGroupView, selectedRange, selectedPosition, recordMetrics, metadata, recordGetterByIndex, updateRecords]);
|
}, [isGroupView, selectedPosition, recordGetterByIndex, updateRecords]);
|
||||||
|
|
||||||
|
const imageCaption = useCallback(() => {
|
||||||
|
const canModifyRow = window.sfMetadataContext.canModifyRow;
|
||||||
|
const summaryColumnKey = PRIVATE_COLUMN_KEY.FILE_SUMMARY;
|
||||||
|
let path = '';
|
||||||
|
let idOldRecordData = {};
|
||||||
|
let idOriginalOldRecordData = {};
|
||||||
|
const { groupRecordIndex, rowIdx } = selectedPosition;
|
||||||
|
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
||||||
|
const fileName = record[PRIVATE_COLUMN_KEY.FILE_NAME];
|
||||||
|
if (Utils.imageCheck(fileName) && canModifyRow(record)) {
|
||||||
|
const parentDir = record[PRIVATE_COLUMN_KEY.PARENT_DIR];
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}, [isGroupView, selectedPosition, recordGetterByIndex, updateRecords]);
|
||||||
|
|
||||||
const handleOptionClick = useCallback((event, option) => {
|
const handleOptionClick = useCallback((event, option) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -215,12 +220,16 @@ const ContextMenu = ({
|
|||||||
generateSummary && generateSummary();
|
generateSummary && generateSummary();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case OPERATION.IMAGE_CAPTION: {
|
||||||
|
imageCaption && imageCaption();
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateSummary]);
|
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateSummary, imageCaption]);
|
||||||
|
|
||||||
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
const getMenuPosition = useCallback((x = 0, y = 0) => {
|
||||||
let menuStyles = {
|
let menuStyles = {
|
||||||
|
@ -2,12 +2,12 @@ import metadataAPI from '../api';
|
|||||||
import { LocalStorage, PRIVATE_COLUMN_KEYS, EDITABLE_DATA_PRIVATE_COLUMN_KEYS,
|
import { LocalStorage, PRIVATE_COLUMN_KEYS, EDITABLE_DATA_PRIVATE_COLUMN_KEYS,
|
||||||
EDITABLE_PRIVATE_COLUMN_KEYS, PREDEFINED_COLUMN_KEYS } from './_basic';
|
EDITABLE_PRIVATE_COLUMN_KEYS, PREDEFINED_COLUMN_KEYS } from './_basic';
|
||||||
import EventBus from '../../components/common/event-bus';
|
import EventBus from '../../components/common/event-bus';
|
||||||
import { username } from '../../utils/constants';
|
import { username, lang } from '../../utils/constants';
|
||||||
|
|
||||||
class Context {
|
class Context {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.settings = {};
|
this.settings = { lang };
|
||||||
this.metadataAPI = null;
|
this.metadataAPI = null;
|
||||||
this.localStorage = null;
|
this.localStorage = null;
|
||||||
this.eventBus = null;
|
this.eventBus = null;
|
||||||
@ -20,7 +20,7 @@ class Context {
|
|||||||
if (this.hasInit) return;
|
if (this.hasInit) return;
|
||||||
|
|
||||||
// init settings
|
// init settings
|
||||||
this.settings = settings || {};
|
this.settings = { ...this.settings, ...settings };
|
||||||
|
|
||||||
// init metadataAPI
|
// init metadataAPI
|
||||||
const { repoInfo } = this.settings;
|
const { repoInfo } = this.settings;
|
||||||
@ -192,9 +192,15 @@ class Context {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ai
|
// ai
|
||||||
generateSummary = (filePaths) => {
|
generateSummary = (filePath) => {
|
||||||
const repoID = this.settings['repoID'];
|
const repoID = this.settings['repoID'];
|
||||||
return this.metadataAPI.generateSummary(repoID, filePaths);
|
return this.metadataAPI.generateSummary(repoID, filePath);
|
||||||
|
};
|
||||||
|
|
||||||
|
imageCaption = (filePath) => {
|
||||||
|
const repoID = this.settings['repoID'];
|
||||||
|
const lang = this.settings['lang'];
|
||||||
|
return this.metadataAPI.imageCaption(repoID, filePath, lang);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
0
seahub/ai/__init__.py
Normal file
0
seahub/ai/__init__.py
Normal file
139
seahub/ai/apis.py
Normal file
139
seahub/ai/apis.py
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import logging
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from pysearpc import SearpcError
|
||||||
|
from seaserv import seafile_api
|
||||||
|
|
||||||
|
from rest_framework.authentication import SessionAuthentication
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from seahub.api2.utils import api_error
|
||||||
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
|
from seahub.utils import get_file_type_and_ext, IMAGE
|
||||||
|
from seahub.views import check_folder_permission
|
||||||
|
from seahub.ai.utils import image_caption, verify_ai_config, generate_summary
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ImageCaption(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')
|
||||||
|
lang = request.data.get('lang')
|
||||||
|
|
||||||
|
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')
|
||||||
|
if not lang:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, 'lang invalid')
|
||||||
|
|
||||||
|
file_type, _ = get_file_type_and_ext(os.path.basename(path))
|
||||||
|
if file_type != IMAGE:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, 'file type not image')
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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,
|
||||||
|
'lang': lang
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = image_caption(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)
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateSummary(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')
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = generate_summary(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)
|
35
seahub/ai/utils.py
Normal file
35
seahub/ai/utils.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import jwt
|
||||||
|
import time
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
from seahub.settings import SEAFILE_AI_SECRET_KEY, SEAFILE_AI_SERVER_URL
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_headers():
|
||||||
|
payload = {'exp': int(time.time()) + 300, }
|
||||||
|
token = jwt.encode(payload, SEAFILE_AI_SECRET_KEY, algorithm='HS256')
|
||||||
|
return {"Authorization": "Token %s" % token}
|
||||||
|
|
||||||
|
|
||||||
|
def verify_ai_config():
|
||||||
|
if not SEAFILE_AI_SERVER_URL or not SEAFILE_AI_SECRET_KEY:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def image_caption(params):
|
||||||
|
headers = gen_headers()
|
||||||
|
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/image-caption/')
|
||||||
|
resp = requests.post(url, json=params, headers=headers, timeout=30)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
def generate_summary(params):
|
||||||
|
headers = gen_headers()
|
||||||
|
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/generate-summary')
|
||||||
|
resp = requests.post(url, json=params, headers=headers, timeout=30)
|
||||||
|
return resp
|
@ -6,17 +6,16 @@ from rest_framework.permissions import IsAuthenticated
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from seahub.api2.utils import api_error, to_python_boolean
|
from seahub.api2.utils import api_error
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.repo_metadata.models import RepoMetadata, RepoMetadataViews
|
from seahub.repo_metadata.models import RepoMetadata, RepoMetadataViews
|
||||||
from seahub.views import check_folder_permission
|
from seahub.views import check_folder_permission
|
||||||
from seahub.repo_metadata.utils import add_init_metadata_task, gen_unique_id, init_metadata, \
|
from seahub.repo_metadata.utils import add_init_metadata_task, gen_unique_id, init_metadata, \
|
||||||
get_sys_columns, update_docs_summary, get_file_download_token
|
get_sys_columns
|
||||||
from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_view_records
|
from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_view_records
|
||||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||||
from seahub.utils.repo import is_repo_admin
|
from seahub.utils.repo import is_repo_admin
|
||||||
from pysearpc import SearpcError
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
|
|
||||||
@ -828,62 +827,3 @@ class MetadataViewsMoveView(APIView):
|
|||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
return Response({'navigation': results['navigation']})
|
return Response({'navigation': results['navigation']})
|
||||||
|
|
||||||
|
|
||||||
class MetadataSummarizeDocs(APIView):
|
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
|
||||||
permission_classes = (IsAuthenticated,)
|
|
||||||
throttle_classes = (UserRateThrottle,)
|
|
||||||
|
|
||||||
def post(self, request, repo_id):
|
|
||||||
file_paths_list = request.data.get('file_paths_list', '')
|
|
||||||
|
|
||||||
if not file_paths_list or not isinstance(file_paths_list, list):
|
|
||||||
error_msg = 'file_paths_list should be a non-empty list..'
|
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
||||||
|
|
||||||
record = RepoMetadata.objects.filter(repo_id=repo_id).first()
|
|
||||||
if not record or not record.enabled:
|
|
||||||
error_msg = f'The metadata module is disabled for repo {repo_id}.'
|
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
|
||||||
|
|
||||||
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, '/')
|
|
||||||
if permission != 'rw':
|
|
||||||
error_msg = 'Permission denied.'
|
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
||||||
|
|
||||||
files_info_list = []
|
|
||||||
for file_path in file_paths_list:
|
|
||||||
try:
|
|
||||||
file_id = seafile_api.get_file_id_by_path(repo_id, file_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 {file_path} not found"
|
|
||||||
)
|
|
||||||
|
|
||||||
if token := get_file_download_token(repo_id, file_id, request.user.username):
|
|
||||||
files_info_list.append(
|
|
||||||
{'file_path': file_path, 'download_token': token}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
error_msg = 'Internal Server Error'
|
|
||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp = update_docs_summary(repo_id, files_info_list)
|
|
||||||
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)
|
|
||||||
|
@ -5,10 +5,11 @@ import json
|
|||||||
import random
|
import random
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from seahub.settings import SECRET_KEY, SEAFEVENTS_SERVER_URL, SEAFILE_AI_SECRET_KEY, SEAFILE_AI_SERVER_URL
|
from seahub.settings import SECRET_KEY, SEAFEVENTS_SERVER_URL
|
||||||
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
|
|
||||||
def add_init_metadata_task(params):
|
def add_init_metadata_task(params):
|
||||||
payload = {'exp': int(time.time()) + 300, }
|
payload = {'exp': int(time.time()) + 300, }
|
||||||
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
|
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
|
||||||
@ -66,17 +67,5 @@ def init_metadata(metadata_server_api):
|
|||||||
metadata_server_api.add_columns(METADATA_TABLE.id, sys_columns)
|
metadata_server_api.add_columns(METADATA_TABLE.id, sys_columns)
|
||||||
|
|
||||||
|
|
||||||
def update_docs_summary(repo_id, files_info_list):
|
|
||||||
payload = {'exp': int(time.time()) + 300, }
|
|
||||||
token = jwt.encode(payload, SEAFILE_AI_SECRET_KEY, algorithm='HS256')
|
|
||||||
headers = {"Authorization": "Token %s" % token}
|
|
||||||
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/update-docs-summary')
|
|
||||||
params = {
|
|
||||||
'repo_id': repo_id,
|
|
||||||
'files_info_list': files_info_list,
|
|
||||||
}
|
|
||||||
resp = requests.post(url, json=params, headers=headers)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def get_file_download_token(repo_id, file_id, username):
|
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)
|
return seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', username, use_onetime=True)
|
||||||
|
@ -257,6 +257,7 @@ INSTALLED_APPS = [
|
|||||||
'seahub.profile',
|
'seahub.profile',
|
||||||
'seahub.share',
|
'seahub.share',
|
||||||
'seahub.help',
|
'seahub.help',
|
||||||
|
'seahub.ai',
|
||||||
'seahub.thumbnail',
|
'seahub.thumbnail',
|
||||||
'seahub.password_session',
|
'seahub.password_session',
|
||||||
'seahub.admin_log',
|
'seahub.admin_log',
|
||||||
|
@ -2,6 +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
|
||||||
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
|
from seahub.api2.endpoints.internal_api import InternalUserListView
|
||||||
from seahub.auth.views import multi_adfs_sso
|
from seahub.auth.views import multi_adfs_sso
|
||||||
@ -209,7 +210,7 @@ from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView,
|
|||||||
Wiki2DuplicatePageView, WikiPageTrashView
|
Wiki2DuplicatePageView, WikiPageTrashView
|
||||||
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
|
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
|
||||||
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
|
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
|
||||||
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataSummarizeDocs, MetadataViewsDuplicateView
|
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView
|
||||||
from seahub.api2.endpoints.user_list import UserListView
|
from seahub.api2.endpoints.user_list import UserListView
|
||||||
|
|
||||||
|
|
||||||
@ -1038,6 +1039,11 @@ if settings.ENABLE_METADATA_MANAGEMENT:
|
|||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/views/$', MetadataViews.as_view(), name='api-v2.1-metadata-views'),
|
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/views/$', MetadataViews.as_view(), name='api-v2.1-metadata-views'),
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/views/(?P<view_id>[-0-9a-zA-Z]{4})/$', MetadataViewsDetailView.as_view(), name='api-v2.1-metadata-views-detail'),
|
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/views/(?P<view_id>[-0-9a-zA-Z]{4})/$', MetadataViewsDetailView.as_view(), name='api-v2.1-metadata-views-detail'),
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/move-views/$', MetadataViewsMoveView.as_view(), name='api-v2.1-metadata-views-move'),
|
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/move-views/$', MetadataViewsMoveView.as_view(), name='api-v2.1-metadata-views-move'),
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/ai/summarize-documents/$', MetadataSummarizeDocs.as_view(), name='api-v2.1-metadata-summarize-documents'),
|
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/duplicate-view/$', MetadataViewsDuplicateView.as_view(), name='api-v2.1-metadata-view-duplicate'),
|
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/duplicate-view/$', MetadataViewsDuplicateView.as_view(), name='api-v2.1-metadata-view-duplicate'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# 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-summary/$', GenerateSummary.as_view(), name='api-v2.1-generate-summary'),
|
||||||
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user