diff --git a/cvat-core/src/api-implementation.ts b/cvat-core/src/api-implementation.ts index 895b692277bf..78a0443648f3 100644 --- a/cvat-core/src/api-implementation.ts +++ b/cvat-core/src/api-implementation.ts @@ -44,7 +44,6 @@ export default function implementAPI(cvat) { cvat.lambda.cancel.implementation = lambdaManager.cancel.bind(lambdaManager); cvat.lambda.listen.implementation = lambdaManager.listen.bind(lambdaManager); cvat.lambda.requests.implementation = lambdaManager.requests.bind(lambdaManager); - cvat.lambda.providers.implementation = lambdaManager.providers.bind(lambdaManager); cvat.server.about.implementation = async () => { const result = await serverProxy.server.about(); @@ -121,8 +120,8 @@ export default function implementAPI(cvat) { return result; }; - cvat.server.request.implementation = async (url, data) => { - const result = await serverProxy.server.request(url, data); + cvat.server.request.implementation = async (url, data, requestConfig) => { + const result = await serverProxy.server.request(url, data, requestConfig); return result; }; diff --git a/cvat-core/src/api.ts b/cvat-core/src/api.ts index c6df9368b3ac..6cdd57187ff5 100644 --- a/cvat-core/src/api.ts +++ b/cvat-core/src/api.ts @@ -111,8 +111,8 @@ function build() { ); return result; }, - async request(url, data) { - const result = await PluginRegistry.apiWrapper(cvat.server.request, url, data); + async request(url, data, requestConfig) { + const result = await PluginRegistry.apiWrapper(cvat.server.request, url, data, requestConfig); return result; }, async setAuthData(response) { @@ -203,10 +203,6 @@ function build() { const result = await PluginRegistry.apiWrapper(cvat.lambda.requests); return result; }, - async providers() { - const result = await PluginRegistry.apiWrapper(cvat.lambda.providers); - return result; - }, }, logger: loggerStorage, config: { diff --git a/cvat-core/src/core-types.ts b/cvat-core/src/core-types.ts index 726adf3327b8..4cc261965c30 100644 --- a/cvat-core/src/core-types.ts +++ b/cvat-core/src/core-types.ts @@ -44,7 +44,6 @@ export interface SerializedModel { return_type?: ModelReturnType; owner?: any; provider?: string; - api_key?: string; url?: string; help_message?: string; animated_gif?: string; diff --git a/cvat-core/src/lambda-manager.ts b/cvat-core/src/lambda-manager.ts index e26a31bb167a..c0c41cb63288 100644 --- a/cvat-core/src/lambda-manager.ts +++ b/cvat-core/src/lambda-manager.ts @@ -6,7 +6,7 @@ import serverProxy from './server-proxy'; import { ArgumentError } from './exceptions'; import MLModel from './ml-model'; -import { ModelProviders, RQStatus } from './enums'; +import { RQStatus } from './enums'; export interface ModelProvider { name: string; @@ -14,37 +14,33 @@ export interface ModelProvider { attributes: Record; } -interface ModelProxy { - run: (body: any) => Promise; - call: (modelID: string | number, body: any) => Promise; - status: (requestID: string) => Promise; - cancel: (requestID: string) => Promise; -} - class LambdaManager { + private cachedList: MLModel[]; private listening: Record void)[]; functionID: string; timeout: number | null; }>; - private cachedList: any; constructor() { this.listening = {}; - this.cachedList = null; + this.cachedList = []; } async list(): Promise<{ models: MLModel[], count: number }> { const lambdaFunctions = await serverProxy.lambda.list(); - const functionsResult = await serverProxy.functions.list(); - const { results: functions, count: functionsCount } = functionsResult; - - const result = [...lambdaFunctions, ...functions]; - const models = result.map((serialzedModel) => new MLModel({ ...serialzedModel })); + const models = []; + for (const model of lambdaFunctions) { + models.push( + new MLModel({ + ...model, + }), + ); + } this.cachedList = models; - return { models, count: lambdaFunctions.length + functionsCount }; + return { models, count: lambdaFunctions.length }; } async run(taskID: number, model: MLModel, args: any) { @@ -68,7 +64,7 @@ class LambdaManager { function: model.id, }; - const result = await LambdaManager.getModelProxy(model).run(body); + const result = await serverProxy.lambda.run(body); return result.id; } @@ -81,16 +77,14 @@ class LambdaManager { ...args, task: taskID, }; - - const result = await LambdaManager.getModelProxy(model).call(model.id, body); + const result = await serverProxy.lambda.call(model.id, body); return result; } async requests() { const lambdaRequests = await serverProxy.lambda.requests(); - const functionsRequests = await serverProxy.functions.requests(); - const result = [...lambdaRequests, ...functionsRequests]; - return result.filter((request) => ['queued', 'started'].includes(request.status)); + return lambdaRequests + .filter((request) => [RQStatus.QUEUED, RQStatus.STARTED].includes(request.status)); } async cancel(requestID, functionID): Promise { @@ -107,7 +101,7 @@ class LambdaManager { delete this.listening[requestID]; } - await LambdaManager.getModelProxy(model).cancel(requestID); + await serverProxy.lambda.cancel(requestID); } async listen( @@ -125,9 +119,8 @@ class LambdaManager { // already listening, avoid sending extra requests return; } - const timeoutCallback = (): void => { - LambdaManager.getModelProxy(model).status(requestID).then((response) => { + serverProxy.lambda.status(requestID).then((response) => { const { status } = response; if (requestID in this.listening) { // check it was not cancelled @@ -171,24 +164,6 @@ class LambdaManager { timeout: window.setTimeout(timeoutCallback), }; } - - async providers(): Promise { - const providersData: Record> = await serverProxy.functions.providers(); - const providers = Object.entries(providersData).map(([provider, attributes]) => { - const { icon } = attributes; - delete attributes.icon; - return { - name: provider, - icon, - attributes, - }; - }); - return providers; - } - - private static getModelProxy(model: MLModel): ModelProxy { - return model.provider === ModelProviders.CVAT ? serverProxy.lambda : serverProxy.functions; - } } export default new LambdaManager(); diff --git a/cvat-core/src/ml-model.ts b/cvat-core/src/ml-model.ts index e03166355fe0..dd7aa2732992 100644 --- a/cvat-core/src/ml-model.ts +++ b/cvat-core/src/ml-model.ts @@ -3,9 +3,7 @@ // // SPDX-License-Identifier: MIT -import serverProxy from './server-proxy'; import PluginRegistry from './plugins'; -import { decodePreview } from './frames'; import { ModelProviders, ModelKind, ModelReturnType, } from './enums'; @@ -105,60 +103,18 @@ export default class MLModel { this.changeToolsBlockerStateCallback = onChangeToolsBlockerState; } - public async save(): Promise { - const result = await PluginRegistry.apiWrapper.call(this, MLModel.prototype.save); - return result; - } - - public async delete(): Promise { - const result = await PluginRegistry.apiWrapper.call(this, MLModel.prototype.delete); - return result; - } - public async preview(): Promise { const result = await PluginRegistry.apiWrapper.call(this, MLModel.prototype.preview); return result; } } -Object.defineProperties(MLModel.prototype.save, { - implementation: { - writable: false, - enumerable: false, - value: async function implementation(this: MLModel): Promise { - const modelData = { - provider: this.provider, - url: this.serialized.url, - api_key: this.serialized.api_key, - }; - - const model = await serverProxy.functions.create(modelData); - return new MLModel(model); - }, - }, -}); - -Object.defineProperties(MLModel.prototype.delete, { - implementation: { - writable: false, - enumerable: false, - value: async function implementation(this: MLModel): Promise { - if (this.isDeletable) { - await serverProxy.functions.delete(this.id as number); - } - }, - }, -}); - Object.defineProperties(MLModel.prototype.preview, { implementation: { writable: false, enumerable: false, - value: async function implementation(this: MLModel): Promise { - if (this.provider === ModelProviders.CVAT) return ''; - const preview = await serverProxy.functions.getPreview(this.id); - if (!preview) return ''; - return decodePreview(preview); + value: async function implementation(): Promise { + return null; }, }, }); diff --git a/cvat-core/src/server-proxy.ts b/cvat-core/src/server-proxy.ts index 38ec0a03227b..3ea3dafae6e7 100644 --- a/cvat-core/src/server-proxy.ts +++ b/cvat-core/src/server-proxy.ts @@ -12,8 +12,8 @@ import { ChunkQuality } from 'cvat-data'; import { SerializedLabel, SerializedAnnotationFormats, ProjectsFilter, SerializedProject, SerializedTask, TasksFilter, SerializedUser, SerializedOrganization, - SerializedAbout, SerializedRemoteFile, SerializedUserAgreement, FunctionsResponseBody, - SerializedRegister, JobsFilter, SerializedJob, SerializedGuide, SerializedAsset, SerializedAcceptInvitation, + SerializedAbout, SerializedRemoteFile, SerializedUserAgreement, + SerializedRegister, JobsFilter, SerializedJob, SerializedGuide, SerializedAsset, SerializedQualitySettingsData, } from './server-response-types'; import { SerializedQualityReportData } from './quality-report'; import { SerializedAnalyticsReport } from './analytics-report'; @@ -569,9 +569,26 @@ async function healthCheck( }); } -async function serverRequest(url: string, data: object): Promise { +export interface ServerRequestConfig { + fetchAll: boolean, +} + +const defaultRequestConfig = { + fetchAll: false, +}; + +async function serverRequest( + url: string, data: object, + requestConfig: ServerRequestConfig = defaultRequestConfig, +): Promise { try { - const res = await Axios(url, data); + let res = null; + const { fetchAll: useFetchAll } = requestConfig; + if (useFetchAll) { + res = await fetchAll(url); + } else { + res = await Axios(url, data); + } return res; } catch (errorData) { throw generateError(errorData); @@ -1626,47 +1643,6 @@ async function getAnnotations(session, id) { return response.data; } -async function getFunctions(): Promise { - const { backendAPI } = config; - - try { - const response = await fetchAll(`${backendAPI}/functions`); - return response; - } catch (errorData) { - if (errorData.response.status === 404) { - return { - results: [], - count: 0, - }; - } - throw generateError(errorData); - } -} - -async function getFunctionProviders() { - const { backendAPI } = config; - - try { - const response = await Axios.get(`${backendAPI}/functions/info`); - return response.data; - } catch (errorData) { - if (errorData.response.status === 404) { - return []; - } - throw generateError(errorData); - } -} - -async function deleteFunction(functionId: number) { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/functions/${functionId}`); - } catch (errorData) { - throw generateError(errorData); - } -} - // Session is 'task' or 'job' async function updateAnnotations(session, id, data, action) { const { backendAPI } = config; @@ -1690,18 +1666,6 @@ async function updateAnnotations(session, id, data, action) { return response.data; } -async function runFunctionRequest(body) { - const { backendAPI } = config; - - try { - const response = await Axios.post(`${backendAPI}/functions/requests/`, body); - - return response.data; - } catch (errorData) { - throw generateError(errorData); - } -} - // Session is 'task' or 'job' async function uploadAnnotations( session, @@ -1789,41 +1753,6 @@ async function uploadAnnotations( } } -async function getFunctionRequestStatus(requestID) { - const { backendAPI } = config; - - try { - const response = await Axios.get(`${backendAPI}/functions/requests/${requestID}`); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } -} - -async function cancelFunctionRequest(requestId: string): Promise { - const { backendAPI } = config; - - try { - await Axios.delete(`${backendAPI}/functions/requests/${requestId}`); - } catch (errorData) { - throw generateError(errorData); - } -} - -async function createFunction(functionData: any) { - const params = enableOrganization(); - const { backendAPI } = config; - - try { - const response = await Axios.post(`${backendAPI}/functions`, functionData, { - params, - }); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } -} - async function saveEvents(events) { const { backendAPI } = config; @@ -1834,31 +1763,6 @@ async function saveEvents(events) { } } -async function callFunction(funId, body) { - const { backendAPI } = config; - - try { - const response = await Axios.post(`${backendAPI}/functions/${funId}/run`, body); - return response.data; - } catch (errorData) { - throw generateError(errorData); - } -} - -async function getFunctionsRequests() { - const { backendAPI } = config; - - try { - const response = await Axios.get(`${backendAPI}/functions/requests/`); - return response.data; - } catch (errorData) { - if (errorData.response.status === 404) { - return []; - } - throw generateError(errorData); - } -} - async function getLambdaFunctions() { const { backendAPI } = config; @@ -2507,19 +2411,6 @@ export default Object.freeze({ cancel: cancelLambdaRequest, }), - functions: Object.freeze({ - list: getFunctions, - status: getFunctionRequestStatus, - requests: getFunctionsRequests, - run: runFunctionRequest, - call: callFunction, - create: createFunction, - providers: getFunctionProviders, - delete: deleteFunction, - cancel: cancelFunctionRequest, - getPreview: getPreview('functions'), - }), - issues: Object.freeze({ create: createIssue, update: updateIssue, diff --git a/cvat-core/src/server-response-types.ts b/cvat-core/src/server-response-types.ts index 2596026c8e8f..aa107d05ffef 100644 --- a/cvat-core/src/server-response-types.ts +++ b/cvat-core/src/server-response-types.ts @@ -7,7 +7,6 @@ import { DimensionType, JobStage, JobState, JobType, ProjectStatus, ShareFileType, TaskMode, TaskStatus, } from 'enums'; -import { SerializedModel } from 'core-types'; export interface SerializedAnnotationImporter { name: string; @@ -23,12 +22,6 @@ export interface SerializedAnnotationFormats { importers: SerializedAnnotationImporter[]; exporters: SerializedAnnotationExporter[]; } - -export interface FunctionsResponseBody { - results: SerializedModel[]; - count: number; -} - export interface ProjectsFilter { page?: number; id?: number; diff --git a/cvat-ui/src/actions/models-actions.ts b/cvat-ui/src/actions/models-actions.ts index 5bac45866b44..233751b0f3d3 100644 --- a/cvat-ui/src/actions/models-actions.ts +++ b/cvat-ui/src/actions/models-actions.ts @@ -4,14 +4,12 @@ // SPDX-License-Identifier: MIT import { ActionUnion, createAction, ThunkAction } from 'utils/redux'; -import { ActiveInference, ModelsQuery } from 'reducers'; import { - getCore, MLModel, ModelProvider, RQStatus, -} from 'cvat-core-wrapper'; + ActiveInference, ModelsQuery, +} from 'reducers'; +import { getCore, MLModel, RQStatus } from 'cvat-core-wrapper'; import { filterNull } from 'utils/filter-null'; -const cvat = getCore(); - export enum ModelsActionTypes { GET_MODELS = 'GET_MODELS', GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS', @@ -46,16 +44,6 @@ export const modelsActions = { getModelsFailed: (error: any) => createAction(ModelsActionTypes.GET_MODELS_FAILED, { error, }), - createModel: () => createAction(ModelsActionTypes.CREATE_MODEL), - createModelSuccess: (model: MLModel) => createAction(ModelsActionTypes.CREATE_MODEL_SUCCESS, { - model, - }), - createModelFailed: (error: any) => createAction(ModelsActionTypes.CREATE_MODEL_FAILED, { error }), - deleteModel: (model: MLModel) => createAction(ModelsActionTypes.DELETE_MODEL, { model }), - deleteModelSuccess: (modelID: string | number) => createAction(ModelsActionTypes.DELETE_MODEL_SUCCESS, { modelID }), - deleteModelFailed: (modelName: string, error: any) => ( - createAction(ModelsActionTypes.DELETE_MODEL_FAILED, { modelName, error }) - ), fetchMetaFailed: (error: any) => createAction(ModelsActionTypes.FETCH_META_FAILED, { error }), getInferenceStatusSuccess: (taskID: number, activeInference: ActiveInference) => ( createAction(ModelsActionTypes.GET_INFERENCE_STATUS_SUCCESS, { @@ -93,12 +81,6 @@ export const modelsActions = { taskInstance, }) ), - getModelProviders: () => createAction(ModelsActionTypes.GET_MODEL_PROVIDERS), - getModelProvidersSuccess: (providers: ModelProvider[]) => ( - createAction(ModelsActionTypes.GET_MODEL_PROVIDERS_SUCCESS, { - providers, - })), - getModelProvidersFailed: (error: any) => createAction(ModelsActionTypes.GET_MODEL_PROVIDERS_FAILED, { error }), getModelPreview: (modelID: string | number) => ( createAction(ModelsActionTypes.GET_MODEL_PREVIEW, { modelID }) ), @@ -129,33 +111,6 @@ export function getModelsAsync(query?: ModelsQuery): ThunkAction { }; } -export function createModelAsync(modelData: Record): ThunkAction { - return async function (dispatch) { - const model = new cvat.classes.MLModel(modelData); - - dispatch(modelsActions.createModel()); - try { - const createdModel = await model.save(); - dispatch(modelsActions.createModelSuccess(createdModel)); - } catch (error) { - dispatch(modelsActions.createModelFailed(error)); - throw error; - } - }; -} - -export function deleteModelAsync(model: MLModel): ThunkAction { - return async function (dispatch) { - dispatch(modelsActions.deleteModel(model)); - try { - await model.delete(); - dispatch(modelsActions.deleteModelSuccess(model.id)); - } catch (error) { - dispatch(modelsActions.deleteModelFailed(model.name, error)); - } - }; -} - interface InferenceMeta { taskID: number; requestID: string; @@ -264,18 +219,6 @@ export function cancelInferenceAsync(taskID: number): ThunkAction { }; } -export function getModelProvidersAsync(): ThunkAction { - return async function (dispatch) { - dispatch(modelsActions.getModelProviders()); - try { - const providers = await cvat.lambda.providers(); - dispatch(modelsActions.getModelProvidersSuccess(providers)); - } catch (error) { - dispatch(modelsActions.getModelProvidersFailed(error)); - } - }; -} - export const getModelPreviewAsync = (model: MLModel): ThunkAction => async (dispatch) => { dispatch(modelsActions.getModelPreview(model.id)); try { diff --git a/cvat-ui/src/actions/plugins-actions.ts b/cvat-ui/src/actions/plugins-actions.ts index 09beb5c50ad0..1c4857bacd7a 100644 --- a/cvat-ui/src/actions/plugins-actions.ts +++ b/cvat-ui/src/actions/plugins-actions.ts @@ -21,7 +21,9 @@ export enum PluginsActionTypes { export const pluginActions = { checkPlugins: () => createAction(PluginsActionTypes.GET_PLUGINS), - checkPluginsSuccess: (list: PluginsList) => createAction(PluginsActionTypes.GET_PLUGINS_SUCCESS, { list }), + checkPluginsSuccess: (list: PluginsList) => createAction( + PluginsActionTypes.GET_PLUGINS_SUCCESS, { list }, + ), checkPluginsFailed: (error: any) => createAction(PluginsActionTypes.GET_PLUGINS_FAILED, { error }), addPlugin: (name: string, destructor: CallableFunction, globalStateDidUpdate?: CallableFunction) => createAction( PluginsActionTypes.ADD_PLUGIN, { name, destructor, globalStateDidUpdate }, diff --git a/cvat-ui/src/components/create-model-page/create-model-page.tsx b/cvat-ui/src/components/create-model-page/create-model-page.tsx deleted file mode 100644 index aee1c6da779c..000000000000 --- a/cvat-ui/src/components/create-model-page/create-model-page.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2022-2023 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import './styles.scss'; - -import React, { useEffect } from 'react'; -import { Row, Col } from 'antd/lib/grid'; -import Text from 'antd/lib/typography/Text'; -import Spin from 'antd/lib/spin'; -import { CombinedState } from 'reducers'; -import { useSelector, useDispatch } from 'react-redux'; -import { getModelProvidersAsync } from 'actions/models-actions'; -import ModelForm from './model-form'; - -function CreateModelPage(): JSX.Element { - const dispatch = useDispatch(); - const fetching = useSelector((state: CombinedState) => state.models.providers.fetching); - const providers = useSelector((state: CombinedState) => state.models.providers.list); - useEffect(() => { - dispatch(getModelProvidersAsync()); - }, []); - - return ( -
- - - Add a model - - - { - fetching ? ( -
- -
- ) : ( - - - - - - ) - } -
- ); -} - -export default React.memo(CreateModelPage); diff --git a/cvat-ui/src/components/create-model-page/model-form.tsx b/cvat-ui/src/components/create-model-page/model-form.tsx deleted file mode 100644 index e81daddf4f72..000000000000 --- a/cvat-ui/src/components/create-model-page/model-form.tsx +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (C) 2023 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import './styles.scss'; - -import React, { useCallback, useState } from 'react'; -import { Store } from 'antd/lib/form/interface'; -import { Row, Col } from 'antd/lib/grid'; -import Form from 'antd/lib/form'; -import Button from 'antd/lib/button'; -import Select from 'antd/lib/select'; -import notification from 'antd/lib/notification'; -import Input from 'antd/lib/input/Input'; - -import { CombinedState } from 'reducers'; -import { useHistory } from 'react-router'; -import { useDispatch, useSelector } from 'react-redux'; -import { createModelAsync } from 'actions/models-actions'; -import { ModelProvider } from 'cvat-core-wrapper'; -import ModelProviderIcon from 'components/models-page/model-provider-icon'; - -interface Props { - providers: ModelProvider[]; -} - -function createProviderFormItems(providerAttributes: Record): JSX.Element { - delete providerAttributes.url; - return ( - <> - { - Object.entries(providerAttributes).map(([key, text]) => ( - - - - )) - } - - ); -} - -function ModelForm(props: Props): JSX.Element { - const { providers } = props; - const providerList = providers.map((provider) => ({ - value: provider.name, - text: provider.name.charAt(0).toUpperCase() + provider.name.slice(1), - })); - const providerMap = Object.fromEntries(providers.map((provider) => [provider.name, provider.attributes])); - - const [form] = Form.useForm(); - const history = useHistory(); - const dispatch = useDispatch(); - const fetching = useSelector((state: CombinedState) => state.models.fetching); - const [currentProviderForm, setCurrentProviderForm] = useState(null); - const onChangeProviderValue = useCallback((provider: string) => { - setCurrentProviderForm(createProviderFormItems(providerMap[provider])); - const emptiedKeys: Record = { ...providerMap[provider] }; - Object.keys(providerMap[provider]).forEach((k) => { emptiedKeys[k] = null; }); - form.setFieldsValue(emptiedKeys); - }, []); - const [providerTouched, setProviderTouched] = useState(false); - const [currentUrlEmpty, setCurrentUrlEmpty] = useState(true); - - const handleSubmit = useCallback(async (): Promise => { - try { - const values: Store = await form.validateFields(); - await dispatch(createModelAsync(values)); - form.resetFields(); - setCurrentProviderForm(null); - setProviderTouched(false); - setCurrentUrlEmpty(true); - notification.info({ - message: 'Model has been successfully created', - className: 'cvat-notification-create-model-success', - }); - // eslint-disable-next-line no-empty - } catch (e) {} - }, []); - - return ( - - -
- - - ) => { - const { value } = event.target; - const guessedProvider = providers.find((provider) => value.includes(provider.name)); - if (guessedProvider && !providerTouched) { - form.setFieldsValue({ provider: guessedProvider.name }); - setCurrentProviderForm(createProviderFormItems(providerMap[guessedProvider.name])); - } - setCurrentUrlEmpty(!value); - }} - /> - - - { - !currentUrlEmpty && ( - <> - - - - {currentProviderForm} - - ) - } -
- - - - - - - - - - - -
- ); -} - -export default React.memo(ModelForm); diff --git a/cvat-ui/src/components/create-model-page/styles.scss b/cvat-ui/src/components/create-model-page/styles.scss deleted file mode 100644 index 7c1a31b67217..000000000000 --- a/cvat-ui/src/components/create-model-page/styles.scss +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2022-2023 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -@import '../../base.scss'; - -.cvat-create-model-page { - width: 100%; - height: 100%; - padding-top: $grid-unit-size * 5; - - .cvat-title { - font-size: 36px; - } -} - -.cvat-create-model-form-wrapper { - margin-top: $grid-unit-size * 3; - height: auto; - border: 1px solid $border-color-1; - border-radius: 3px; - padding: $grid-unit-size * 3; - background: $background-color-1; - text-align: initial; -} - -.cvat-create-models-actions { - margin-top: $grid-unit-size * 2; -} - -.cvat-model-provider-icon { - display: flex; - - img { - margin-top: 3px; - margin-right: $grid-unit-size; - width: $grid-unit-size * 2; - height: $grid-unit-size * 2; - } -} - -.cvat-select-model-provider { - img { - margin-top: 7px; - } -} diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index db57686c161d..71ee785cf988 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -75,7 +75,6 @@ import EventRecorder from 'utils/controls-logger'; import EmailConfirmationPage from './email-confirmation-pages/email-confirmed'; import EmailVerificationSentPage from './email-confirmation-pages/email-verification-sent'; import IncorrectEmailConfirmationPage from './email-confirmation-pages/incorrect-email-confirmation'; -import CreateModelPage from './create-model-page/create-model-page'; import CreateJobPage from './create-job-page/create-job-page'; import AnalyticsPage from './analytics-page/analytics-page'; @@ -503,7 +502,6 @@ class CVATApplication extends React.PureComponent - )} diff --git a/cvat-ui/src/components/models-page/deployed-model-item.tsx b/cvat-ui/src/components/models-page/deployed-model-item.tsx index f8ef7c41cf44..06544852657a 100644 --- a/cvat-ui/src/components/models-page/deployed-model-item.tsx +++ b/cvat-ui/src/components/models-page/deployed-model-item.tsx @@ -3,23 +3,26 @@ // // SPDX-License-Identifier: MIT -import React, { useCallback, useState } from 'react'; +import React, { useState } from 'react'; import { Row, Col } from 'antd/lib/grid'; + import Tag from 'antd/lib/tag'; import Text from 'antd/lib/typography/Text'; import { MoreOutlined } from '@ant-design/icons'; import Modal from 'antd/lib/modal'; -import { MLModel, ModelProviders } from 'cvat-core-wrapper'; import Title from 'antd/lib/typography/Title'; import Meta from 'antd/lib/card/Meta'; -import Preview from 'components/common/preview'; import moment from 'moment'; import Divider from 'antd/lib/divider'; import Card from 'antd/lib/card'; import Dropdown from 'antd/lib/dropdown'; import Button from 'antd/lib/button'; -import ModelActionsMenuComponent from './models-action-menu'; -import ModelProviderIcon from './model-provider-icon'; +import Menu from 'antd/lib/menu'; + +import Preview from 'components/common/preview'; +import { usePlugins } from 'utils/hooks'; +import { CombinedState } from 'reducers'; +import { MLModel, ModelProviders } from 'cvat-core-wrapper'; interface Props { model: MLModel; @@ -27,26 +30,56 @@ interface Props { export default function DeployedModelItem(props: Props): JSX.Element { const { model } = props; - const { provider } = model; - const [isRemoved, setIsRemoved] = useState(false); const [isModalShown, setIsModalShown] = useState(false); - const onOpenModel = () => { + const onOpenModel = (): void => { setIsModalShown(true); }; - const onCloseModel = () => { + const onCloseModel = (): void => { setIsModalShown(false); }; - const onDelete = useCallback(() => { - setIsRemoved(true); - }, []); - const created = moment(model.createdDate).fromNow(); - const icon = ; const modelDescription = model.provider !== ModelProviders.CVAT ? {`Added ${created}`} : System model; + + const menuItems: [JSX.Element, number][] = []; + const topBarItems: [JSX.Element, number][] = []; + + const menuPlugins = usePlugins( + (state: CombinedState) => state.plugins.components.modelsPage.modelItem.menu.items, props, + ); + menuItems.push( + ...menuPlugins.map(({ component: Component, weight }, index) => ( + [, weight] as [JSX.Element, number] + )), + ); + const modelMenu = ( + + {menuItems.sort((menuItem1, menuItem2) => menuItem1[1] - menuItem2[1]) + .map((menuItem) => menuItem[0])} + + ); + + const topBarProps = { + provider: model.provider, + }; + const topBarPlugins = usePlugins( + (state: CombinedState) => state.plugins.components.modelsPage.modelItem.topBar.menu.items, topBarProps, + ); + topBarItems.push( + ...topBarPlugins.map(({ component: Component, weight }, index) => ( + [, weight] as [JSX.Element, number] + )), + ); + const modelTopBar = ( +
+ {topBarItems.sort((item1, item2) => item1[1] - item2[1]) + .map((item) => item[0])} +
+ ); + return ( <> - {icon ?
{icon}
: null} + { modelTopBar }
{model.name} {modelDescription} @@ -129,7 +162,7 @@ export default function DeployedModelItem(props: Props): JSX.Element { /> )} size='small' - className={`cvat-models-item-card ${isRemoved ? 'cvat-models-item-card-removed' : ''} `} + className='cvat-models-item-card' > { - model.provider !== ModelProviders.CVAT && ( - }> + menuItems.length !== 0 && ( +
)} /> - { - icon ?
{icon}
: null - } + { modelTopBar } ); diff --git a/cvat-ui/src/components/models-page/model-provider-icon.tsx b/cvat-ui/src/components/models-page/model-provider-icon.tsx deleted file mode 100644 index 49bb0443c271..000000000000 --- a/cvat-ui/src/components/models-page/model-provider-icon.tsx +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2023 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { ModelProvider } from 'cvat-core-wrapper'; -import { CombinedState } from 'reducers'; -import { useSelector } from 'react-redux'; - -interface Props { - providerName: string; -} - -export default function ModelProviderIcon(props: Props): JSX.Element | null { - const { providerName } = props; - const providers = useSelector((state: CombinedState) => state.models.providers.list); - - let icon: JSX.Element | null = null; - const providerInstance = providers.find((_provider: ModelProvider) => _provider.name === providerName); - if (providerInstance) { - icon = ( - {providerName} - ); - } - return icon; -} diff --git a/cvat-ui/src/components/models-page/models-action-menu.tsx b/cvat-ui/src/components/models-page/models-action-menu.tsx deleted file mode 100644 index c2b63b1b87b9..000000000000 --- a/cvat-ui/src/components/models-page/models-action-menu.tsx +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2023 CVAT.ai Corporation -// -// SPDX-License-Identifier: MIT - -import React, { useCallback } from 'react'; -import { useDispatch } from 'react-redux'; -import Modal from 'antd/lib/modal'; -import Menu from 'antd/lib/menu'; -import { MLModel, ModelProviders } from 'cvat-core-wrapper'; -import { deleteModelAsync } from 'actions/models-actions'; - -interface Props { - model: MLModel; - onDelete: () => void; -} - -export default function ModelActionsMenuComponent(props: Props): JSX.Element { - const { model, onDelete } = props; - const { provider } = model; - const cvatProvider = provider === ModelProviders.CVAT; - - const dispatch = useDispatch(); - - const onDeleteModel = useCallback((): void => { - Modal.confirm({ - title: `The model ${model.name} will be deleted`, - content: 'You will not be able to use it anymore. Continue?', - className: 'cvat-modal-confirm-remove-model', - onOk: () => { - dispatch(deleteModelAsync(model)); - onDelete(); - }, - okButtonProps: { - type: 'primary', - danger: true, - }, - okText: 'Delete', - }); - }, []); - - const onOpenUrl = useCallback((): void => { - window.open(model.url, '_blank'); - }, []); - - return ( - - { - !cvatProvider && ( - - Open model URL - - ) - } - { - !cvatProvider && ( - <> - - - Delete - - - ) - } - - ); -} diff --git a/cvat-ui/src/components/models-page/models-page.tsx b/cvat-ui/src/components/models-page/models-page.tsx index 40159a8b5c4d..dc8665492b90 100644 --- a/cvat-ui/src/components/models-page/models-page.tsx +++ b/cvat-ui/src/components/models-page/models-page.tsx @@ -4,10 +4,10 @@ // SPDX-License-Identifier: MIT import './styles.scss'; -import React, { useCallback, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { useHistory } from 'react-router'; import { useDispatch, useSelector } from 'react-redux'; -import { getModelProvidersAsync, getModelsAsync } from 'actions/models-actions'; +import { getModelsAsync } from 'actions/models-actions'; import { updateHistoryFromQuery } from 'components/resource-sorting-filtering'; import Spin from 'antd/lib/spin'; import notification from 'antd/lib/notification'; @@ -24,10 +24,6 @@ function ModelsPageComponent(): JSX.Element { const query = useSelector((state: CombinedState) => state.models.query); const totalCount = useSelector((state: CombinedState) => state.models.totalCount); - const onCreateModel = useCallback(() => { - history.push('/models/create'); - }, []); - const updatedQuery = { ...query }; const queryParams = new URLSearchParams(history.location.search); for (const key of Object.keys(updatedQuery)) { @@ -44,7 +40,6 @@ function ModelsPageComponent(): JSX.Element { const pageOutOfBounds = totalCount && updatedQuery.page > Math.ceil(totalCount / PAGE_SIZE); useEffect(() => { - dispatch(getModelProvidersAsync()); dispatch(getModelsAsync(updatedQuery)); if (pageOutOfBounds) { notification.error({ @@ -63,7 +58,6 @@ function ModelsPageComponent(): JSX.Element { { dispatch( getModelsAsync({ diff --git a/cvat-ui/src/components/models-page/styles.scss b/cvat-ui/src/components/models-page/styles.scss index 3ed33ff4b4e0..55a4da98d6f5 100644 --- a/cvat-ui/src/components/models-page/styles.scss +++ b/cvat-ui/src/components/models-page/styles.scss @@ -92,7 +92,6 @@ > div { display: flex; - margin-right: $grid-unit-size * 4; > button { margin-right: $grid-unit-size; @@ -100,10 +99,6 @@ } } -.cvat-models-add-wrapper { - display: inline-block; -} - .cvat-model-delete { position: absolute; top: $grid-unit-size; @@ -159,21 +154,14 @@ } } -.cvat-model-item-provider { +.cvat-model-item-top-bar { + width: 100%; position: absolute; - top: $grid-unit-size; - right: $grid-unit-size; -} - -.cvat-model-item-provider-inner { - @extend .cvat-model-item-provider; - - right: $grid-unit-size * 2; - - svg { - width: $grid-unit-size * 4; - height: $grid-unit-size * 4; - } + display: flex; + justify-content: flex-end; + top: 0; + left: 0; + padding: $grid-unit-size; } .cvat-models-item-description { diff --git a/cvat-ui/src/components/models-page/top-bar.tsx b/cvat-ui/src/components/models-page/top-bar.tsx index 45cd44320d3c..2aa11d13fca8 100644 --- a/cvat-ui/src/components/models-page/top-bar.tsx +++ b/cvat-ui/src/components/models-page/top-bar.tsx @@ -4,11 +4,10 @@ import React, { useState } from 'react'; import { Row, Col } from 'antd/lib/grid'; -import { PlusOutlined } from '@ant-design/icons'; -import Button from 'antd/lib/button'; import { Input } from 'antd'; import { SortingComponent, ResourceFilterHOC, defaultVisibility } from 'components/resource-sorting-filtering'; -import { ModelsQuery } from 'reducers'; +import { CombinedState, ModelsQuery } from 'reducers'; +import { usePlugins } from 'utils/hooks'; import { localStorageRecentKeyword, localStorageRecentCapacity, config, } from './models-filter-configuration'; @@ -22,15 +21,23 @@ interface VisibleTopBarProps { onApplySorting(sorting: string | null): void; onApplySearch(search: string | null): void; query: ModelsQuery; - onCreateModel(): void; disabled?: boolean; } export default function TopBarComponent(props: VisibleTopBarProps): JSX.Element { const { - query, onApplyFilter, onApplySorting, onApplySearch, onCreateModel, disabled, + query, onApplyFilter, onApplySorting, onApplySearch, disabled, } = props; const [visibility, setVisibility] = useState(defaultVisibility); + const plugins = usePlugins((state: CombinedState) => state.plugins.components.modelsPage.topBar.items, props); + const controls = []; + if (plugins.length) { + controls.push( + ...plugins.map(({ component: Component }, index) => ( + + )), + ); + } return ( @@ -80,9 +87,7 @@ export default function TopBarComponent(props: VisibleTopBarProps): JSX.Element /> -
-
+ {controls}
); diff --git a/cvat-ui/src/components/plugins-entrypoint.tsx b/cvat-ui/src/components/plugins-entrypoint.tsx index 48fe5dfb2981..76dc92f1e461 100644 --- a/cvat-ui/src/components/plugins-entrypoint.tsx +++ b/cvat-ui/src/components/plugins-entrypoint.tsx @@ -8,18 +8,25 @@ import { useDispatch } from 'react-redux'; import { PluginsActionTypes, pluginActions } from 'actions/plugins-actions'; import { getCore, APIWrapperEnterOptions } from 'cvat-core-wrapper'; +import { modelsActions } from 'actions/models-actions'; const core = getCore(); +export type PluginActionCreators = { + getModelsSuccess: typeof modelsActions['getModelsSuccess'], +}; + export type ComponentBuilder = ({ dispatch, REGISTER_ACTION, REMOVE_ACTION, + actionCreators, core, }: { dispatch: Dispatch, REGISTER_ACTION: PluginsActionTypes.ADD_UI_COMPONENT, - REMOVE_ACTION: PluginsActionTypes.REMOVE_UI_COMPONENT + REMOVE_ACTION: PluginsActionTypes.REMOVE_UI_COMPONENT, + actionCreators: PluginActionCreators, core: any, }) => { name: string; @@ -43,6 +50,9 @@ function PluginEntrypoint(): null { dispatch, REGISTER_ACTION: PluginsActionTypes.ADD_UI_COMPONENT, REMOVE_ACTION: PluginsActionTypes.REMOVE_UI_COMPONENT, + actionCreators: { + getModelsSuccess: modelsActions.getModelsSuccess, + }, core, }); diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index 295355bf8794..8c73572c3471 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -6,7 +6,7 @@ import { Canvas3d } from 'cvat-canvas3d/src/typescript/canvas3d'; import { Canvas, RectDrawingMethod, CuboidDrawingMethod } from 'cvat-canvas-wrapper'; import { - Webhook, MLModel, ModelProvider, Organization, + Webhook, MLModel, Organization, QualityReport, QualityConflict, QualitySettings, FramesMetaData, RQStatus, EventLogger, } from 'cvat-core-wrapper'; import { IntelligentScissors } from 'utils/opencv-wrapper/intelligent-scissors'; @@ -291,6 +291,21 @@ export interface PluginsState { loginPage: { loginForm: PluginComponent[]; }; + modelsPage: { + topBar: { + items: PluginComponent[], + }, + modelItem: { + menu: { + items: PluginComponent[], + }, + topBar:{ + menu: { + items: PluginComponent[], + } + }, + } + }; projectActions: { items: PluginComponent[]; }; @@ -399,10 +414,6 @@ export interface ModelsState { modelRunnerIsVisible: boolean; modelRunnerTask: any; query: ModelsQuery; - providers: { - fetching: boolean; - list: ModelProvider[]; - } previews: { [index: string]: Preview; }; diff --git a/cvat-ui/src/reducers/models-reducer.ts b/cvat-ui/src/reducers/models-reducer.ts index 7ecaf8f099ad..1a9b11456296 100644 --- a/cvat-ui/src/reducers/models-reducer.ts +++ b/cvat-ui/src/reducers/models-reducer.ts @@ -29,10 +29,6 @@ const defaultState: ModelsState = { filter: null, sort: null, }, - providers: { - fetching: false, - list: [], - }, previews: {}, }; @@ -78,36 +74,6 @@ export default function (state = defaultState, action: ModelsActions | AuthActio fetching: false, }; } - case ModelsActionTypes.CREATE_MODEL: { - return { - ...state, - fetching: true, - }; - } - case ModelsActionTypes.CREATE_MODEL_FAILED: { - return { - ...state, - fetching: false, - }; - } - case ModelsActionTypes.CREATE_MODEL_SUCCESS: { - const mutual = { - ...state, - fetching: false, - }; - - if (action.payload.model.kind === ModelKind.REID) { - return { - ...mutual, - reid: [...state.reid, action.payload.model], - }; - } - - return { - ...mutual, - [`${action.payload.model.kind}s`]: [...state[`${action.payload.model.kind}s`], action.payload.model], - }; - } case ModelsActionTypes.SHOW_RUN_MODEL_DIALOG: { return { ...state, @@ -165,33 +131,6 @@ export default function (state = defaultState, action: ModelsActions | AuthActio inferences: { ...inferences }, }; } - case ModelsActionTypes.GET_MODEL_PROVIDERS: { - return { - ...state, - providers: { - ...state.providers, - fetching: true, - }, - }; - } - case ModelsActionTypes.GET_MODEL_PROVIDERS_SUCCESS: { - return { - ...state, - providers: { - fetching: false, - list: action.payload.providers, - }, - }; - } - case ModelsActionTypes.GET_MODEL_PROVIDERS_FAILED: { - return { - ...state, - providers: { - ...state.providers, - fetching: false, - }, - }; - } case ModelsActionTypes.GET_MODEL_PREVIEW: { const { modelID } = action.payload; const { previews } = state; diff --git a/cvat-ui/src/reducers/notifications-reducer.ts b/cvat-ui/src/reducers/notifications-reducer.ts index 743718612e09..c0e3761d70ee 100644 --- a/cvat-ui/src/reducers/notifications-reducer.ts +++ b/cvat-ui/src/reducers/notifications-reducer.ts @@ -802,39 +802,6 @@ export default function (state = defaultState, action: AnyAction): Notifications }, }; } - case ModelsActionTypes.CREATE_MODEL_FAILED: { - return { - ...state, - errors: { - ...state.errors, - models: { - ...state.errors.models, - creating: { - message: 'Could not create model', - reason: action.payload.error, - shouldLog: !(action.payload.error instanceof ServerError), - }, - }, - }, - }; - } - case ModelsActionTypes.DELETE_MODEL_FAILED: { - const { modelName } = action.payload; - return { - ...state, - errors: { - ...state.errors, - models: { - ...state.errors.models, - deleting: { - message: `Could not delete model ${modelName}`, - reason: action.payload.error, - shouldLog: !(action.payload.error instanceof ServerError), - }, - }, - }, - }; - } case AnnotationActionTypes.GET_JOB_FAILED: { return { ...state, diff --git a/cvat-ui/src/reducers/plugins-reducer.ts b/cvat-ui/src/reducers/plugins-reducer.ts index 6c0ec2379c0f..97531f43b217 100644 --- a/cvat-ui/src/reducers/plugins-reducer.ts +++ b/cvat-ui/src/reducers/plugins-reducer.ts @@ -23,6 +23,21 @@ const defaultState: PluginsState = { loginPage: { loginForm: [], }, + modelsPage: { + topBar: { + items: [], + }, + modelItem: { + menu: { + items: [], + }, + topBar: { + menu: { + items: [], + }, + }, + }, + }, projectActions: { items: [], },