Skip to content

Commit

Permalink
search file use ai
Browse files Browse the repository at this point in the history
  • Loading branch information
JoinTyang committed Oct 18, 2023
1 parent 4f925b1 commit 5a67bda
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 39 deletions.
182 changes: 154 additions & 28 deletions frontend/src/components/search/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,30 @@ import PropTypes from 'prop-types';
import isHotkey from 'is-hotkey';
import MediaQuery from 'react-responsive';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot, username } from '../../utils/constants';
import { gettext, siteRoot, username, enableSeafileAI } from '../../utils/constants';
import SearchResultItem from './search-result-item';
import { Utils } from '../../utils/utils';
import { isMac } from '../../utils/extra-attributes';
import toaster from '../toast';

const INDEX_STATE = {
RUNNING: 'running',
UNCREATED: 'uncreated',
FINISHED: 'finished'
};

const SEARCH_MODE = {
SIMILARITY: 'similarity',
NORMAL: 'normal',
};

const propTypes = {
repoID: PropTypes.string,
placeholder: PropTypes.string,
onSearchedClick: PropTypes.func.isRequired,
isPublic: PropTypes.bool,
isLibView: PropTypes.bool,
repoName: PropTypes.string,
};

const PER_PAGE = 10;
Expand All @@ -37,7 +50,9 @@ class Search extends Component {
isResultGetted: false,
isCloseShow: false,
isSearchInputShow: false, // for mobile
searchPageUrl: this.baseSearchPageURL
searchPageUrl: this.baseSearchPageURL,
searchMode: SEARCH_MODE.NORMAL,
indexState: INDEX_STATE.UNCREATED,
};
this.inputValue = '';
this.highlightRef = null;
Expand Down Expand Up @@ -215,32 +230,71 @@ class Search extends Component {
this.updateSearchPageURL(queryData);
queryData['per_page'] = PER_PAGE;
queryData['page'] = page;
seafileAPI.searchFiles(queryData, cancelToken).then(res => {
this.source = null;
if (res.data.total > 0) {
this.setState({
resultItems: [...this.state.resultItems, ...this.formatResultItems(res.data.results)],
isResultGetted: true,
isLoading: false,
page: page + 1,
hasMore: res.data.has_more,
});
} else {
this.setState({
highlightIndex: 0,
resultItems: [],
isLoading: false,
isResultGetted: true,
hasMore: res.data.has_more,
});
}
}).catch(error => {
/* eslint-disable */
console.log(error);
/* eslint-enable */
this.setState({ isLoading: false });
});
if (this.state.searchMode === SEARCH_MODE.NORMAL) {
this.onNarmalSearch(queryData, cancelToken, page);
} else {
this.onSimilaritySearch(queryData, cancelToken, page);
}
}
};

onNarmalSearch = (queryData, cancelToken, page) => {
seafileAPI.searchFiles(queryData, cancelToken).then(res => {
this.source = null;
if (res.data.total > 0) {
this.setState({
resultItems: [...this.state.resultItems, ...this.formatResultItems(res.data.results)],
isResultGetted: true,
isLoading: false,
page: page + 1,
hasMore: res.data.has_more,
});
} else {
this.setState({
highlightIndex: 0,
resultItems: [],
isLoading: false,
isResultGetted: true,
hasMore: res.data.has_more,
});
}
}).catch(error => {
/* eslint-disable */
console.log(error);
/* eslint-enable */
this.setState({ isLoading: false });
});
};

onSimilaritySearch = (queryData, cancelToken, page) => {
if (this.state.indexState !== INDEX_STATE.FINISHED) {
toaster.danger(gettext('Please create index first.'));
}
seafileAPI.similaritySearchFiles(queryData, cancelToken).then(res => {
this.source = null;
if (res.data && res.data.children_list.length > 0) {
this.setState({
resultItems: [...this.state.resultItems, ...this.formatSimilarityItems(res.data.children_list)],
isResultGetted: true,
isLoading: false,
page: page + 1,
hasMore: res.data.has_more,
});
} else {
this.setState({
highlightIndex: 0,
resultItems: [],
isLoading: false,
isResultGetted: true,
hasMore: res.data.has_more,
});
}
}).catch(error => {
/* eslint-disable */
console.log(error);
/* eslint-enable */
this.setState({ isLoading: false });
});
};

onResultListScroll = (e) => {
Expand Down Expand Up @@ -299,6 +353,24 @@ class Search extends Component {
return items;
}

formatSimilarityItems(data) {
let items = [];
let repo_id = this.props.repoID;
for (let i = 0; i < data.length; i++) {
items[i] = {};
items[i]['index'] = [i];
items[i]['name'] = data[i].path.substring(data[i].path.lastIndexOf('/')+1);
items[i]['path'] = data[i].path;
items[i]['repo_id'] = repo_id;
items[i]['repo_name'] = this.props.repoName;
items[i]['is_dir'] = false;
items[i]['link_content'] = decodeURI(data[i].path).substring(1);
items[i]['content'] = data[i].sentence;
items[i]['thumbnail_url'] = '';
}
return items;
}

resetToDefault() {
this.inputValue = null;
this.setState({
Expand Down Expand Up @@ -354,10 +426,55 @@ class Search extends Component {
});
};

onChangeSearchMode = (searchMode) => {
this.setState({
searchMode: searchMode
});

if (searchMode === SEARCH_MODE.SIMILARITY && this.state.indexState === INDEX_STATE.UNCREATED) {
this.libraryIndexState();
}
};

libraryIndexState = () => {
seafileAPI.queryLibraryIndexState(this.props.repoID).then(res => {
this.setState({indexState: res.data.state});
}).catch(error => {
/* eslint-disable */
console.log(error);
/* eslint-enable */
});
};

onCreateIndex = () => {
this.setState({ indexState: INDEX_STATE.RUNNING });
seafileAPI.createLibraryIndex(this.props.repoID).then(res => {
const taskId = res.data.task_id;
this.timer = setInterval(() => {
seafileAPI.queryIndexTaskStatus(taskId).then(res => {
const is_finished = res.data.is_finished;
if (is_finished) {
this.setState({ indexState: INDEX_STATE.FINISHED });
this.timer && clearInterval(this.timer);
this.timer = null;
}
}).catch(error => {
this.timer && clearInterval(this.timer);
this.timer = null;
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
});
}, 3000);
}).catch(error => {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
});
};

render() {
let width = this.state.width !== 'default' ? this.state.width : '';
let style = {'width': width};
const { searchPageUrl, isMaskShow } = this.state;
const { searchPageUrl, isMaskShow, searchMode, indexState } = this.state;
const placeholder = `${this.props.placeholder}${isMaskShow ? '' : ` (${controlKey} + f )`}`;
return (
<Fragment>
Expand Down Expand Up @@ -391,6 +508,15 @@ class Search extends Component {
onScroll={this.onResultListScroll}
ref={this.searchContainer}
>
{this.state.isCloseShow && enableSeafileAI && this.props.isLibView &&
<div>
<button onClick={() => this.onChangeSearchMode(SEARCH_MODE.NORMAL)} >{gettext('Normal search')}</button>
<button onClick={() => this.onChangeSearchMode(SEARCH_MODE.SIMILARITY)}>{gettext('Similarity search')}</button>
{searchMode === SEARCH_MODE.SIMILARITY &&
<button onClick={this.onCreateIndex} disabled={indexState !== INDEX_STATE.UNCREATED}>{gettext('Open index')}</button>
}
</div>
}
{this.renderSearchResult()}
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/toolbar/common-toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class CommonToolbar extends React.Component {
repoID={repoID}
placeholder={this.props.searchPlaceholder || gettext('Search files')}
onSearchedClick={this.props.onSearchedClick}
isLibView={this.props.isLibView}
repoName={repoName}
/>
)}
{this.props.isLibView && !isPro &&
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ export const enableVideoThumbnail = window.app.pageOptions.enableVideoThumbnail;
export const enableOnlyoffice = window.app.pageOptions.enableOnlyoffice || false;
export const onlyofficeConverterExtensions = window.app.pageOptions.onlyofficeConverterExtensions || [];

// seafile_ai
export const enableSeafileAI = window.app.pageOptions.enableSeafileAI || false;

// dtable
export const workspaceID = window.app.pageOptions.workspaceID;
export const showLogoutIcon = window.app.pageOptions.showLogoutIcon;
Expand Down
26 changes: 25 additions & 1 deletion seahub/ai/apis.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
from seahub.views import check_folder_permission
from seahub.utils.repo import parse_repo_perm
from seahub.ai.utils import create_library_sdoc_index, get_dir_file_recursively, similarity_search_in_library, \
update_library_sdoc_index, delete_library_index, query_task_status, get_dir_sdoc_info_list
update_library_sdoc_index, delete_library_index, query_task_status, get_dir_sdoc_info_list, \
query_library_index_state

from seaserv import seafile_api

Expand Down Expand Up @@ -218,6 +219,29 @@ def get(self, request):
return Response(resp_json, resp.status_code)


class LibraryIndexState(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, )
throttle_classes = (UserRateThrottle, )

def get(self, request):
repo_id = request.GET.get('repo_id')

if not repo_id:
return api_error(status.HTTP_400_BAD_REQUEST, 'repo_id invalid')
try:
resp = query_library_index_state(repo_id)
if resp.status_code == 500:
logger.error('query library index state error status: %s body: %s', resp.status_code, resp.text)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
resp_json = resp.json()
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')

return Response(resp_json, resp.status_code)


class RepoFiles(APIView):
authentication_classes = (SeafileAiAuthentication, )
throttle_classes = (UserRateThrottle, )
Expand Down
7 changes: 7 additions & 0 deletions seahub/ai/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,10 @@ def query_task_status(task_id):
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/task-status/')
resp = requests.get(url, headers=headers, params={'task_id': task_id})
return resp


def query_library_index_state(associate_id):
headers = gen_headers()
url = urljoin(SEAFILE_AI_SERVER_URL, '/api/v1/library-index-state/')
resp = requests.get(url, headers=headers, params={'associate_id': associate_id})
return resp
5 changes: 3 additions & 2 deletions seahub/base/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
MEDIA_ROOT, SHOW_LOGOUT_ICON, CUSTOM_LOGO_PATH, CUSTOM_FAVICON_PATH, \
ENABLE_SEAFILE_DOCS, LOGIN_BG_IMAGE_PATH, \
CUSTOM_LOGIN_BG_PATH, ENABLE_SHARE_LINK_REPORT_ABUSE, \
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK, ENABLE_SEADOC
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK, ENABLE_SEADOC, ENABLE_SEAFILE_AI

from seahub.organizations.models import OrgAdminSettings
from seahub.organizations.settings import ORG_ENABLE_ADMIN_CUSTOM_LOGO
Expand Down Expand Up @@ -165,7 +165,8 @@ def base(request):
'side_nav_footer_custom_html': SIDE_NAV_FOOTER_CUSTOM_HTML,
'about_dialog_custom_html': ABOUT_DIALOG_CUSTOM_HTML,
'enable_repo_auto_del': ENABLE_REPO_AUTO_DEL,
'enable_seadoc': ENABLE_SEADOC
'enable_seadoc': ENABLE_SEADOC,
'enable_seafile_ai': ENABLE_SEAFILE_AI,
}

if request.user.is_staff:
Expand Down
1 change: 1 addition & 0 deletions seahub/templates/base_for_react.html
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
enableOnlyoffice: {% if enableOnlyoffice %} true {% else %} false {% endif %},
onlyofficeConverterExtensions: {% if onlyofficeConverterExtensions %} {{onlyofficeConverterExtensions|safe}} {% else %} null {% endif %},
enableSeadoc: {% if enable_seadoc %} true {% else %} false {% endif %},
enableSeafileAI: {% if enable_seafile_ai %} true {% else %} false {% endif %},
}
};
</script>
Expand Down
20 changes: 12 additions & 8 deletions seahub/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@

from seahub.ocm.settings import OCM_ENDPOINT

from seahub.ai.apis import LibrarySdocIndexes, SimilaritySearchInLibrary, LibrarySdocIndex, RepoFiles, TaskStatus
from seahub.ai.apis import LibrarySdocIndexes, SimilaritySearchInLibrary, LibrarySdocIndex, RepoFiles, TaskStatus, \
LibraryIndexState

urlpatterns = [
path('accounts/', include('seahub.base.registration_urls')),
Expand Down Expand Up @@ -535,13 +536,6 @@
re_path(r'api/v2.1/ocm/providers/(?P<provider_id>[-0-9a-f]{36})/repos/(?P<repo_id>[-0-9a-f]{36})/download-link/$', OCMReposDownloadLinkView.as_view(), name='api-v2.1-ocm-repos-download-link'),
re_path(r'api/v2.1/ocm/providers/(?P<provider_id>[-0-9a-f]{36})/repos/(?P<repo_id>[-0-9a-f]{36})/upload-link/$', OCMReposUploadLinkView.as_view(), name='api-v2.1-ocm-repos-upload-link'),

# seafile-ai
re_path(r'^api/v2.1/ai/library-sdoc-indexes/$', LibrarySdocIndexes.as_view(), name='api-v2.1-ai-library-sdoc-indexes'),
re_path(r'^api/v2.1/ai/similarity-search-in-library/$', SimilaritySearchInLibrary.as_view(), name='api-v2.1-ai-similarity-search-in-library'),
re_path(r'^api/v2.1/ai/library-sdoc-index/$', LibrarySdocIndex.as_view(), name='api-v2.1-ai-library-sdoc-index'),
re_path(r'^api/v2.1/ai/repo/files/$', RepoFiles.as_view(), name='api-v2.1-ai-repo-files'),
re_path(r'^api/v2.1/ai/task-status/$', TaskStatus.as_view(), name='api-v2.1-ai-task-status'),

# admin: activities
re_path(r'^api/v2.1/admin/user-activities/$', UserActivitiesView.as_view(), name='api-v2.1-admin-user-activity'),

Expand Down Expand Up @@ -964,3 +958,13 @@
urlpatterns += [
re_path(r'^api/v2.1/seadoc/', include('seahub.seadoc.urls')),
]

if settings.ENABLE_SEAFILE_AI:
urlpatterns += [
re_path(r'^api/v2.1/ai/library-sdoc-indexes/$', LibrarySdocIndexes.as_view(), name='api-v2.1-ai-library-sdoc-indexes'),
re_path(r'^api/v2.1/ai/similarity-search-in-library/$', SimilaritySearchInLibrary.as_view(), name='api-v2.1-ai-similarity-search-in-library'),
re_path(r'^api/v2.1/ai/library-sdoc-index/$', LibrarySdocIndex.as_view(), name='api-v2.1-ai-library-sdoc-index'),
re_path(r'^api/v2.1/ai/repo/files/$', RepoFiles.as_view(), name='api-v2.1-ai-repo-files'),
re_path(r'^api/v2.1/ai/task-status/$', TaskStatus.as_view(), name='api-v2.1-ai-task-status'),
re_path(r'^api/v2.1/ai/library-index-state/$', LibraryIndexState.as_view(), name='api-v2.1-ai-library-index-state'),
]

0 comments on commit 5a67bda

Please sign in to comment.