React & Antd UI: Model manager (#856)
* Supported git to create and sync * Updated antd * Updated icons * Improved header * Top bar for models & empty models list * Removed one extra reducer and actions * Removed one extra reducer and actions * Crossplatform css * Models reducers, some models actions, base for model list, imrovements * Models list, ability to delete models * Added ability to upload models * Improved form, reinit models after create * Removed some importants in css * Model running dialog window, a lot of fixesmain
parent
73e5327bad
commit
9f63686baf
@ -0,0 +1,417 @@
|
||||
import { AnyAction, Dispatch, ActionCreator } from 'redux';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
|
||||
import getCore from '../core';
|
||||
import { getCVATStore } from '../store';
|
||||
import { Model, ModelFiles, CombinedState } from '../reducers/interfaces';
|
||||
|
||||
export enum ModelsActionTypes {
|
||||
GET_MODELS = 'GET_MODELS',
|
||||
GET_MODELS_SUCCESS = 'GET_MODELS_SUCCESS',
|
||||
GET_MODELS_FAILED = 'GET_MODELS_FAILED',
|
||||
DELETE_MODEL = 'DELETE_MODEL',
|
||||
DELETE_MODEL_SUCCESS = 'DELETE_MODEL_SUCCESS',
|
||||
DELETE_MODEL_FAILED = 'DELETE_MODEL_FAILED',
|
||||
CREATE_MODEL = 'CREATE_MODEL',
|
||||
CREATE_MODEL_SUCCESS = 'CREATE_MODEL_SUCCESS',
|
||||
CREATE_MODEL_FAILED = 'CREATE_MODEL_FAILED',
|
||||
CREATE_MODEL_STATUS_UPDATED = 'CREATE_MODEL_STATUS_UPDATED',
|
||||
INFER_MODEL = 'INFER_MODEL',
|
||||
INFER_MODEL_SUCCESS = 'INFER_MODEL_SUCCESS',
|
||||
INFER_MODEL_FAILED = 'INFER_MODEL_FAILED',
|
||||
GET_INFERENCE_STATUS = 'GET_INFERENCE_STATUS',
|
||||
GET_INFERENCE_STATUS_SUCCESS = 'GET_INFERENCE_STATUS_SUCCESS',
|
||||
GET_INFERENCE_STATUS_FAILED = 'GET_INFERENCE_STATUS_FAILED',
|
||||
SHOW_RUN_MODEL_DIALOG = 'SHOW_RUN_MODEL_DIALOG',
|
||||
CLOSE_RUN_MODEL_DIALOG = 'CLOSE_RUN_MODEL_DIALOG',
|
||||
}
|
||||
|
||||
export enum PreinstalledModels {
|
||||
RCNN = 'RCNN Object Detector',
|
||||
MaskRCNN = 'Mask RCNN Object Detector',
|
||||
}
|
||||
|
||||
const core = getCore();
|
||||
const baseURL = core.config.backendAPI.slice(0, -7);
|
||||
|
||||
function getModels(): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.GET_MODELS,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function getModelsSuccess(models: Model[]): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.GET_MODELS_SUCCESS,
|
||||
payload: {
|
||||
models,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function getModelsFailed(error: any): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.GET_MODELS_FAILED,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function getModelsAsync():
|
||||
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
const store = getCVATStore();
|
||||
const state: CombinedState = store.getState();
|
||||
const OpenVINO = state.plugins.plugins.AUTO_ANNOTATION;
|
||||
const RCNN = state.plugins.plugins.TF_ANNOTATION;
|
||||
const MaskRCNN = state.plugins.plugins.TF_SEGMENTATION;
|
||||
|
||||
dispatch(getModels());
|
||||
const models: Model[] = [];
|
||||
|
||||
try {
|
||||
if (OpenVINO) {
|
||||
const response = await core.server.request(
|
||||
`${baseURL}/auto_annotation/meta/get`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: JSON.stringify([]),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
for (const model of response.models) {
|
||||
models.push({
|
||||
id: model.id,
|
||||
ownerID: model.owner,
|
||||
primary: model.primary,
|
||||
name: model.name,
|
||||
uploadDate: model.uploadDate,
|
||||
updateDate: model.updateDate,
|
||||
labels: [...model.labels],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (RCNN) {
|
||||
models.push({
|
||||
id: null,
|
||||
ownerID: null,
|
||||
primary: true,
|
||||
name: PreinstalledModels.RCNN,
|
||||
uploadDate: '',
|
||||
updateDate: '',
|
||||
labels: ['surfboard', 'car', 'skateboard', 'boat', 'clock',
|
||||
'cat', 'cow', 'knife', 'apple', 'cup', 'tv',
|
||||
'baseball_bat', 'book', 'suitcase', 'tennis_racket',
|
||||
'stop_sign', 'couch', 'cell_phone', 'keyboard',
|
||||
'cake', 'tie', 'frisbee', 'truck', 'fire_hydrant',
|
||||
'snowboard', 'bed', 'vase', 'teddy_bear',
|
||||
'toaster', 'wine_glass', 'traffic_light',
|
||||
'broccoli', 'backpack', 'carrot', 'potted_plant',
|
||||
'donut', 'umbrella', 'parking_meter', 'bottle',
|
||||
'sandwich', 'motorcycle', 'bear', 'banana',
|
||||
'person', 'scissors', 'elephant', 'dining_table',
|
||||
'toothbrush', 'toilet', 'skis', 'bowl', 'sheep',
|
||||
'refrigerator', 'oven', 'microwave', 'train',
|
||||
'orange', 'mouse', 'laptop', 'bench', 'bicycle',
|
||||
'fork', 'kite', 'zebra', 'baseball_glove', 'bus',
|
||||
'spoon', 'horse', 'handbag', 'pizza', 'sports_ball',
|
||||
'airplane', 'hair_drier', 'hot_dog', 'remote',
|
||||
'sink', 'dog', 'bird', 'giraffe', 'chair',
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (MaskRCNN) {
|
||||
models.push({
|
||||
id: null,
|
||||
ownerID: null,
|
||||
primary: true,
|
||||
name: PreinstalledModels.MaskRCNN,
|
||||
uploadDate: '',
|
||||
updateDate: '',
|
||||
labels: ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
|
||||
'bus', 'train', 'truck', 'boat', 'traffic light',
|
||||
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird',
|
||||
'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear',
|
||||
'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie',
|
||||
'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
|
||||
'kite', 'baseball bat', 'baseball glove', 'skateboard',
|
||||
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',
|
||||
'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
|
||||
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
|
||||
'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed',
|
||||
'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
|
||||
'keyboard', 'cell phone', 'microwave', 'oven', 'toaster',
|
||||
'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
|
||||
'teddy bear', 'hair drier', 'toothbrush',
|
||||
],
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(getModelsFailed(error));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(getModelsSuccess(models));
|
||||
};
|
||||
}
|
||||
|
||||
function deleteModel(id: number): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.DELETE_MODEL,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function deleteModelSuccess(id: number): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.DELETE_MODEL_SUCCESS,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function deleteModelFailed(id: number, error: any): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.DELETE_MODEL_FAILED,
|
||||
payload: {
|
||||
error,
|
||||
id,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function deleteModelAsync(id: number): ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
dispatch(deleteModel(id));
|
||||
try {
|
||||
await core.server.request(`${baseURL}/auto_annotation/delete/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch(deleteModelFailed(id, error));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(deleteModelSuccess(id));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function createModel(): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.CREATE_MODEL,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function createModelSuccess(): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.CREATE_MODEL_SUCCESS,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function createModelFailed(error: any): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.CREATE_MODEL_FAILED,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function createModelUpdateStatus(status: string): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.CREATE_MODEL_STATUS_UPDATED,
|
||||
payload: {
|
||||
status,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function createModelAsync(name: string, files: ModelFiles, global: boolean):
|
||||
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
async function checkCallback(id: string): Promise<void> {
|
||||
try {
|
||||
const data = await core.server.request(
|
||||
`${baseURL}/auto_annotation/check/${id}`, {
|
||||
method: 'GET',
|
||||
},
|
||||
);
|
||||
|
||||
switch (data.status) {
|
||||
case 'failed':
|
||||
dispatch(createModelFailed(
|
||||
`Checking request has returned the "${data.status}" status. Message: ${data.error}`,
|
||||
));
|
||||
break;
|
||||
case 'unknown':
|
||||
dispatch(createModelFailed(
|
||||
`Checking request has returned the "${data.status}" status.`,
|
||||
));
|
||||
break;
|
||||
case 'finished':
|
||||
dispatch(createModelSuccess());
|
||||
break;
|
||||
default:
|
||||
if ('progress' in data) {
|
||||
createModelUpdateStatus(data.progress);
|
||||
}
|
||||
setTimeout(checkCallback.bind(null, id), 1000);
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(createModelFailed(error));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(createModel());
|
||||
const data = new FormData();
|
||||
data.append('name', name);
|
||||
data.append('storage', typeof files.bin === 'string' ? 'shared' : 'local');
|
||||
data.append('shared', global.toString());
|
||||
Object.keys(files).reduce((acc, key: string): FormData => {
|
||||
acc.append(key, files[key]);
|
||||
return acc;
|
||||
}, data);
|
||||
|
||||
try {
|
||||
dispatch(createModelUpdateStatus('Request is beign sent..'));
|
||||
const response = await core.server.request(
|
||||
`${baseURL}/auto_annotation/create`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
},
|
||||
);
|
||||
|
||||
dispatch(createModelUpdateStatus('Request is being processed..'));
|
||||
setTimeout(checkCallback.bind(null, response.id), 1000);
|
||||
} catch (error) {
|
||||
dispatch(createModelFailed(error));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function inferModel(): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.INFER_MODEL,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function inferModelSuccess(): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.INFER_MODEL_SUCCESS,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function inferModelFailed(error: any): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.INFER_MODEL_FAILED,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function inferModelAsync(
|
||||
taskInstance: any,
|
||||
model: Model,
|
||||
mapping: {
|
||||
[index: string]: string;
|
||||
},
|
||||
cleanOut: boolean,
|
||||
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
dispatch(inferModel());
|
||||
|
||||
try {
|
||||
if (model.name === PreinstalledModels.RCNN) {
|
||||
await core.server.request(
|
||||
`${baseURL}/tensorflow/annotation/create/task/${taskInstance.id}`,
|
||||
);
|
||||
} else if (model.name === PreinstalledModels.MaskRCNN) {
|
||||
await core.server.request(
|
||||
`${baseURL}/tensorflow/segmentation/create/task/${taskInstance.id}`,
|
||||
);
|
||||
} else {
|
||||
await core.server.request(
|
||||
`${baseURL}/auto_annotation/start/${model.id}/${taskInstance.id}`, {
|
||||
method: 'POST',
|
||||
data: JSON.stringify({
|
||||
reset: cleanOut,
|
||||
labels: mapping,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(inferModelFailed(error));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(inferModelSuccess());
|
||||
};
|
||||
}
|
||||
|
||||
export function closeRunModelDialog(): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function showRunModelDialog(taskInstance: any): AnyAction {
|
||||
const action = {
|
||||
type: ModelsActionTypes.SHOW_RUN_MODEL_DIALOG,
|
||||
payload: {
|
||||
taskInstance,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
import { AnyAction, Dispatch, ActionCreator } from 'redux';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
|
||||
import getCore from '../core';
|
||||
|
||||
const core = getCore();
|
||||
|
||||
export enum TaskActionTypes {
|
||||
GET_TASK = 'GET_TASK',
|
||||
GET_TASK_SUCCESS = 'GET_TASK_SUCCESS',
|
||||
GET_TASK_FAILED = 'GET_TASK_FAILED',
|
||||
UPDATE_TASK = 'UPDATE_TASK',
|
||||
UPDATE_TASK_SUCCESS = 'UPDATE_TASK_SUCCESS',
|
||||
UPDATE_TASK_FAILED = 'UPDATE_TASK_FAILED',
|
||||
}
|
||||
|
||||
function getTask(): AnyAction {
|
||||
const action = {
|
||||
type: TaskActionTypes.GET_TASK,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function getTaskSuccess(taskInstance: any, previewImage: string): AnyAction {
|
||||
const action = {
|
||||
type: TaskActionTypes.GET_TASK_SUCCESS,
|
||||
payload: {
|
||||
taskInstance,
|
||||
previewImage,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function getTaskFailed(error: any): AnyAction {
|
||||
const action = {
|
||||
type: TaskActionTypes.GET_TASK_FAILED,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function getTaskAsync(tid: number):
|
||||
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
try {
|
||||
dispatch(getTask());
|
||||
const taskInstance = (await core.tasks.get({ id: tid }))[0];
|
||||
if (taskInstance) {
|
||||
const previewImage = await taskInstance.frames.preview();
|
||||
dispatch(getTaskSuccess(taskInstance, previewImage));
|
||||
} else {
|
||||
throw Error(`Task ${tid} wasn't found on the server`);
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(getTaskFailed(error));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function updateTask(): AnyAction {
|
||||
const action = {
|
||||
type: TaskActionTypes.UPDATE_TASK,
|
||||
payload: {},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function updateTaskSuccess(taskInstance: any): AnyAction {
|
||||
const action = {
|
||||
type: TaskActionTypes.UPDATE_TASK_SUCCESS,
|
||||
payload: {
|
||||
taskInstance,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
function updateTaskFailed(error: any, taskInstance: any): AnyAction {
|
||||
const action = {
|
||||
type: TaskActionTypes.UPDATE_TASK_FAILED,
|
||||
payload: {
|
||||
error,
|
||||
taskInstance,
|
||||
},
|
||||
};
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
export function updateTaskAsync(taskInstance: any):
|
||||
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
try {
|
||||
dispatch(updateTask());
|
||||
await taskInstance.save();
|
||||
const [task] = await core.tasks.get({ id: taskInstance.id });
|
||||
dispatch(updateTaskSuccess(task));
|
||||
} catch (error) {
|
||||
// try abort all changes
|
||||
let task = null;
|
||||
try {
|
||||
[task] = await core.tasks.get({ id: taskInstance.id });
|
||||
} catch (fetchError) {
|
||||
dispatch(updateTaskFailed(error, taskInstance));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(updateTaskFailed(error, task));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// a job is a part of a task, so for simplify we consider
|
||||
// updating the job as updating a task
|
||||
export function updateJobAsync(jobInstance: any):
|
||||
ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
try {
|
||||
dispatch(updateTask());
|
||||
await jobInstance.save();
|
||||
const [task] = await core.tasks.get({ id: jobInstance.task.id });
|
||||
dispatch(updateTaskSuccess(task));
|
||||
} catch (error) {
|
||||
// try abort all changes
|
||||
let task = null;
|
||||
try {
|
||||
[task] = await core.tasks.get({ id: jobInstance.task.id });
|
||||
} catch (fetchError) {
|
||||
dispatch(updateTaskFailed(error, jobInstance.task));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(updateTaskFailed(error, task));
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Icon,
|
||||
Alert,
|
||||
Button,
|
||||
Tooltip,
|
||||
Modal,
|
||||
message,
|
||||
} from 'antd';
|
||||
|
||||
import CreateModelForm, {
|
||||
CreateModelForm as WrappedCreateModelForm
|
||||
} from './create-model-form';
|
||||
import ConnectedFileManager, {
|
||||
FileManagerContainer
|
||||
} from '../../containers/file-manager/file-manager';
|
||||
import { ModelFiles } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
createModel(name: string, files: ModelFiles, global: boolean): void;
|
||||
isAdmin: boolean;
|
||||
modelCreatingError: string;
|
||||
modelCreatingStatus: string;
|
||||
}
|
||||
|
||||
export default class CreateModelContent extends React.PureComponent<Props> {
|
||||
private modelForm: WrappedCreateModelForm;
|
||||
private fileManagerContainer: FileManagerContainer;
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
this.modelForm = null as any as WrappedCreateModelForm;
|
||||
this.fileManagerContainer = null as any as FileManagerContainer;
|
||||
}
|
||||
|
||||
private handleSubmitClick = () => {
|
||||
this.modelForm.submit()
|
||||
.then((data) => {
|
||||
const {
|
||||
local,
|
||||
share,
|
||||
} = this.fileManagerContainer.getFiles();
|
||||
|
||||
const files = local.length ? local : share;
|
||||
const grouppedFiles: ModelFiles = {
|
||||
xml: '',
|
||||
bin: '',
|
||||
py: '',
|
||||
json: '',
|
||||
};
|
||||
|
||||
(files as any).reduce((acc: ModelFiles, value: File | string): ModelFiles => {
|
||||
const name = typeof value === 'string' ? value : value.name;
|
||||
const [extension] = name.split('.').reverse();
|
||||
if (extension in acc) {
|
||||
acc[extension] = value;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, grouppedFiles);
|
||||
|
||||
if (Object.keys(grouppedFiles)
|
||||
.map((key: string) => grouppedFiles[key])
|
||||
.filter((val) => !!val).length !== 4) {
|
||||
Modal.error({
|
||||
title: 'Could not upload a model',
|
||||
content: 'Please, specify correct files',
|
||||
});
|
||||
} else {
|
||||
this.props.createModel(data.name, grouppedFiles, data.global);
|
||||
}
|
||||
}).catch(() => {
|
||||
Modal.error({
|
||||
title: 'Could not upload a model',
|
||||
content: 'Please, check input fields',
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.modelCreatingStatus !== 'CREATED'
|
||||
&& this.props.modelCreatingStatus === 'CREATED') {
|
||||
message.success('The model has been uploaded');
|
||||
this.modelForm.resetFields();
|
||||
this.fileManagerContainer.reset();
|
||||
}
|
||||
|
||||
if (!prevProps.modelCreatingError && this.props.modelCreatingError) {
|
||||
Modal.error({
|
||||
title: 'Could not create task',
|
||||
content: this.props.modelCreatingError,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const loading = !!this.props.modelCreatingStatus
|
||||
&& this.props.modelCreatingStatus !== 'CREATED';
|
||||
const status = this.props.modelCreatingStatus
|
||||
&& this.props.modelCreatingStatus !== 'CREATED' ? this.props.modelCreatingStatus : '';
|
||||
|
||||
const guideLink = 'https://github.com/opencv/cvat/tree/develop/cvat/apps/auto_annotation';
|
||||
return (
|
||||
<Row type='flex' justify='start' align='middle' className='cvat-create-model-content'>
|
||||
<Col span={24}>
|
||||
<Tooltip overlay='Click to open guide'>
|
||||
<Icon onClick={() => {window.open(guideLink, '_blank')}} type='question-circle'/>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<CreateModelForm
|
||||
wrappedComponentRef={
|
||||
(ref: WrappedCreateModelForm) => this.modelForm = ref
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ConnectedFileManager ref={
|
||||
(container: FileManagerContainer) =>
|
||||
this.fileManagerContainer = container
|
||||
}/>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
{status && <Alert message={`${status}`}/>}
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Button
|
||||
type='danger'
|
||||
disabled={loading}
|
||||
loading={loading}
|
||||
onClick={this.handleSubmitClick}
|
||||
> Submit </Button>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Form,
|
||||
Input,
|
||||
Tooltip,
|
||||
Checkbox,
|
||||
} from 'antd';
|
||||
|
||||
import { FormComponentProps } from 'antd/lib/form/Form';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
type Props = FormComponentProps;
|
||||
|
||||
export class CreateModelForm extends React.PureComponent<Props> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public submit(): Promise<{name: string, global: boolean}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.props.form.validateFields((errors, values) => {
|
||||
if (!errors) {
|
||||
resolve({
|
||||
name: values.name,
|
||||
global: values.global,
|
||||
});
|
||||
} else {
|
||||
reject(errors);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public resetFields() {
|
||||
this.props.form.resetFields();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { getFieldDecorator } = this.props.form;
|
||||
|
||||
return (
|
||||
<Form onSubmit={(e: React.FormEvent) => e.preventDefault()}>
|
||||
<Row type='flex'>
|
||||
<Col>
|
||||
<Text type='secondary'>Name</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col span={14}>
|
||||
<Form.Item>
|
||||
{ getFieldDecorator('name', {
|
||||
rules: [{
|
||||
required: true,
|
||||
message: 'Please, specify a model name',
|
||||
}],
|
||||
})(<Input placeholder='Model name'/>)}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8} offset={2}>
|
||||
<Form.Item>
|
||||
<Tooltip overlay='Will this model be availabe for everyone?'>
|
||||
{ getFieldDecorator('global', {
|
||||
initialValue: false,
|
||||
valuePropName: 'checked',
|
||||
})(<Checkbox>
|
||||
<Text className='cvat-black-color'>
|
||||
Load globally
|
||||
</Text>
|
||||
</Checkbox>)}
|
||||
</Tooltip>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Form.create()(CreateModelForm);
|
||||
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
import CreateModelContent from './create-model-content';
|
||||
import { ModelFiles } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
createModel(name: string, files: ModelFiles, global: boolean): void;
|
||||
isAdmin: boolean;
|
||||
modelCreatingError: string;
|
||||
modelCreatingStatus: string;
|
||||
}
|
||||
|
||||
export default function CreateModelPageComponent(props: Props) {
|
||||
return (
|
||||
<Row type='flex' justify='center' align='top' className='cvat-create-model-form-wrapper'>
|
||||
<Col md={20} lg={16} xl={14} xxl={9}>
|
||||
<Text className='cvat-title'>{`Upload a new model`}</Text>
|
||||
<CreateModelContent
|
||||
isAdmin={props.isAdmin}
|
||||
modelCreatingError={props.modelCreatingError}
|
||||
modelCreatingStatus={props.modelCreatingStatus}
|
||||
createModel={props.createModel}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,340 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Tag,
|
||||
Spin,
|
||||
Icon,
|
||||
Modal,
|
||||
Select,
|
||||
Tooltip,
|
||||
Checkbox,
|
||||
} from 'antd';
|
||||
|
||||
import { Model } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
modelsInitialized: boolean;
|
||||
models: Model[];
|
||||
activeProcesses: {
|
||||
[index: string]: string;
|
||||
};
|
||||
visible: boolean;
|
||||
taskInstance: any;
|
||||
startingError: string;
|
||||
getModels(): void;
|
||||
closeDialog(): void;
|
||||
runInference(
|
||||
taskInstance: any,
|
||||
model: Model,
|
||||
mapping: {
|
||||
[index: string]: string
|
||||
},
|
||||
cleanOut: boolean,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
selectedModel: string | null;
|
||||
cleanOut: boolean;
|
||||
mapping: {
|
||||
[index: string]: string;
|
||||
};
|
||||
colors: {
|
||||
[index: string]: string;
|
||||
};
|
||||
matching: {
|
||||
model: string,
|
||||
task: string,
|
||||
};
|
||||
}
|
||||
|
||||
const nextColor = (function *() {
|
||||
const values = [
|
||||
'magenta', 'green', 'geekblue',
|
||||
'orange', 'red', 'cyan',
|
||||
'blue', 'volcano', 'purple',
|
||||
];
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
yield values[i];
|
||||
if (i === values.length) {
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
export default class ModelRunnerModalComponent extends React.PureComponent<Props, State> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedModel: null,
|
||||
mapping: {},
|
||||
colors: {},
|
||||
cleanOut: false,
|
||||
matching: {
|
||||
model: '',
|
||||
task: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private renderModelSelector() {
|
||||
return (
|
||||
<Row type='flex' align='middle'>
|
||||
<Col span={4}>Model:</Col>
|
||||
<Col span={19}>
|
||||
<Select
|
||||
placeholder='Select a model'
|
||||
style={{width: '100%'}}
|
||||
onChange={(value: string) => this.setState({
|
||||
selectedModel: value,
|
||||
mapping: {},
|
||||
})
|
||||
}>
|
||||
{this.props.models.map((model) =>
|
||||
<Select.Option key={model.name}>
|
||||
{model.name}
|
||||
</Select.Option>
|
||||
)}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
private renderMappingTag(modelLabel: string, taskLabel: string) {
|
||||
return (
|
||||
<Row key={`${modelLabel}-${taskLabel}`} type='flex' justify='start' align='middle'>
|
||||
<Col span={10}>
|
||||
<Tag color={this.state.colors[modelLabel]}>{modelLabel}</Tag>
|
||||
</Col>
|
||||
<Col span={10} offset={1}>
|
||||
<Tag color={this.state.colors[modelLabel]}>{taskLabel}</Tag>
|
||||
</Col>
|
||||
<Col span={1} offset={1}>
|
||||
<Tooltip overlay='Remove the mapped values'>
|
||||
<Icon
|
||||
className='cvat-run-model-dialog-remove-mapping-icon'
|
||||
type='close-circle'
|
||||
onClick={() => {
|
||||
const mapping = {...this.state.mapping};
|
||||
delete mapping[modelLabel];
|
||||
this.setState({
|
||||
mapping,
|
||||
});
|
||||
}}/>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
private renderMappingInputSelector(
|
||||
value: string,
|
||||
current: string,
|
||||
options: string[]
|
||||
) {
|
||||
return (
|
||||
<Select
|
||||
value={value}
|
||||
placeholder={`${current} labels`}
|
||||
style={{width: '100%'}}
|
||||
onChange={(value: string) => {
|
||||
const anotherValue = current === 'Model' ?
|
||||
this.state.matching.task : this.state.matching.model;
|
||||
|
||||
if (!anotherValue) {
|
||||
const matching = { ...this.state.matching };
|
||||
if (current === 'Model') {
|
||||
matching.model = value;
|
||||
} else {
|
||||
matching.task = value;
|
||||
}
|
||||
this.setState({
|
||||
matching,
|
||||
});
|
||||
} else {
|
||||
const colors = {...this.state.colors};
|
||||
const mapping = {...this.state.mapping};
|
||||
|
||||
if (current === 'Model') {
|
||||
colors[value] = nextColor.next().value;
|
||||
mapping[value] = anotherValue;
|
||||
} else {
|
||||
colors[anotherValue] = nextColor.next().value;
|
||||
mapping[anotherValue] = value;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
colors,
|
||||
mapping,
|
||||
matching: {
|
||||
task: '',
|
||||
model: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
{options.map((label: string) =>
|
||||
<Select.Option key={label}>
|
||||
{label}
|
||||
</Select.Option>
|
||||
)}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
private renderMappingInput(availableModelLabels: string[], availableTaskLabels: string[]) {
|
||||
return (
|
||||
<Row type='flex' justify='start' align='middle'>
|
||||
<Col span={10}>
|
||||
{this.renderMappingInputSelector(
|
||||
this.state.matching.model,
|
||||
'Model',
|
||||
availableModelLabels,
|
||||
)}
|
||||
</Col>
|
||||
<Col span={10} offset={1}>
|
||||
{this.renderMappingInputSelector(
|
||||
this.state.matching.task,
|
||||
'Task',
|
||||
availableTaskLabels,
|
||||
)}
|
||||
</Col>
|
||||
<Col span={1} offset={1}>
|
||||
<Tooltip overlay='Specify a label mapping between model labels and task labels'>
|
||||
<Icon className='cvat-run-model-dialog-info-icon' type='question-circle'/>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent() {
|
||||
const model = this.state.selectedModel && this.props.models
|
||||
.filter((model) => model.name === this.state.selectedModel)[0];
|
||||
|
||||
const excludedLabels: {
|
||||
model: string[],
|
||||
task: string[],
|
||||
} = {
|
||||
model: [],
|
||||
task: [],
|
||||
};
|
||||
|
||||
const withMapping = model && !model.primary;
|
||||
const tags = withMapping ? Object.keys(this.state.mapping)
|
||||
.map((modelLabel: string) => {
|
||||
const taskLabel = this.state.mapping[modelLabel];
|
||||
excludedLabels.model.push(modelLabel);
|
||||
excludedLabels.task.push(taskLabel);
|
||||
return this.renderMappingTag(
|
||||
modelLabel,
|
||||
this.state.mapping[modelLabel],
|
||||
);
|
||||
}) : [];
|
||||
|
||||
const availableModelLabels = model ? model.labels
|
||||
.filter(
|
||||
(label: string) => !excludedLabels.model.includes(label),
|
||||
) : [];
|
||||
const availableTaskLabels = this.props.taskInstance.labels
|
||||
.map(
|
||||
(label: any) => label.name,
|
||||
).filter((label: string) => !excludedLabels.task.includes(label))
|
||||
|
||||
const mappingISAvailable = !!availableModelLabels.length
|
||||
&& !!availableTaskLabels.length;
|
||||
|
||||
return (
|
||||
<div className='cvat-run-model-dialog'>
|
||||
{ this.renderModelSelector() }
|
||||
{ withMapping && tags}
|
||||
{ withMapping
|
||||
&& mappingISAvailable
|
||||
&& this.renderMappingInput(availableModelLabels, availableTaskLabels)
|
||||
}
|
||||
{ withMapping &&
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={this.state.cleanOut}
|
||||
onChange={(e: any) => this.setState({
|
||||
cleanOut: e.target.checked,
|
||||
})}
|
||||
> Clean old annotations </Checkbox>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSpin() {
|
||||
return (
|
||||
<Spin size='large' style={{margin: '25% 50%'}}/>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
if (!prevProps.visible && this.props.visible) {
|
||||
this.setState({
|
||||
selectedModel: null,
|
||||
mapping: {},
|
||||
matching: {
|
||||
model: '',
|
||||
task: '',
|
||||
},
|
||||
cleanOut: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (!prevProps.startingError && this.props.startingError) {
|
||||
Modal.error({
|
||||
title: 'Could not start model inference',
|
||||
content: this.props.startingError,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (!this.props.modelsInitialized) {
|
||||
this.props.getModels();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const activeModel = this.props.models.filter(
|
||||
(model) => model.name === this.state.selectedModel
|
||||
)[0];
|
||||
|
||||
let enabledSubmit = !!activeModel
|
||||
&& activeModel.primary || !!Object.keys(this.state.mapping).length;
|
||||
|
||||
return (
|
||||
this.props.visible && <Modal
|
||||
closable={false}
|
||||
okType='danger'
|
||||
okText='Submit'
|
||||
onOk={() => {
|
||||
this.props.runInference(
|
||||
this.props.taskInstance,
|
||||
this.props.models
|
||||
.filter((model) => model.name === this.state.selectedModel)[0],
|
||||
this.state.mapping,
|
||||
this.state.cleanOut,
|
||||
);
|
||||
this.props.closeDialog()
|
||||
}}
|
||||
onCancel={() => this.props.closeDialog()}
|
||||
okButtonProps={{disabled: !enabledSubmit}}
|
||||
title='Automatic annotation'
|
||||
visible={true}
|
||||
>
|
||||
{!this.props.modelsInitialized && this.renderSpin()}
|
||||
{this.props.modelsInitialized && this.renderContent()}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Tag,
|
||||
Select,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
import { Model } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
model: Model;
|
||||
}
|
||||
|
||||
export default function BuiltModelItemComponent(props: Props) {
|
||||
return (
|
||||
<Row className='cvat-models-list-item' type='flex'>
|
||||
<Col span={4} xxl={3}>
|
||||
<Tag color='orange'>Tensorflow</Tag>
|
||||
</Col>
|
||||
<Col span={6} xxl={7}>
|
||||
<Text className='cvat-black-color'>
|
||||
{props.model.name}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col span={5} offset={7}>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder='Supported labels'
|
||||
style={{width: '90%'}}
|
||||
value='Supported labels'
|
||||
>
|
||||
{props.model.labels.map(
|
||||
(label) => <Select.Option key={label}>
|
||||
{label}
|
||||
</Select.Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={2}/>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
import BuiltModelItemComponent from './built-model-item';
|
||||
import { Model } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
models: Model[];
|
||||
}
|
||||
|
||||
export default function IntegratedModelsListComponent(props: Props) {
|
||||
const items = props.models.map((model) =>
|
||||
<BuiltModelItemComponent key={model.name} model={model}/>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={22} lg={18} xl={16} xxl={14}>
|
||||
<Text className='cvat-black-color' strong>Primary</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={22} lg={18} xl={16} xxl={14} className='cvat-models-list'>
|
||||
<Row type='flex' align='middle' style={{padding: '10px'}}>
|
||||
<Col span={4} xxl={3}>
|
||||
<Text strong>{'Framework'}</Text>
|
||||
</Col>
|
||||
<Col span={6} xxl={7}>
|
||||
<Text strong>{'Name'}</Text>
|
||||
</Col>
|
||||
<Col span={5} offset={7}>
|
||||
<Text strong>Labels</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
{ items }
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
export default function EmptyListComponent() {
|
||||
const emptyTasksIcon = () => (<img src='/assets/empty-tasks-icon.svg'/>);
|
||||
|
||||
return (
|
||||
<div className='cvat-empty-models-list'>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col>
|
||||
<Icon className='cvat-empty-models-icon' component={emptyTasksIcon}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col>
|
||||
<Text strong>{'No models uploaded yet ...'}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col>
|
||||
<Text type='secondary'>{'To annotate your tasks automatically'}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col>
|
||||
<Link to='/models/create'>{'upload a new model'}</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Spin,
|
||||
} from 'antd';
|
||||
|
||||
import TopBarComponent from './top-bar';
|
||||
import UploadedModelsList from './uploaded-models-list';
|
||||
import BuiltModelsList from './built-models-list';
|
||||
import EmptyListComponent from './empty-list';
|
||||
import { Model } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
installedAutoAnnotation: boolean;
|
||||
installedTFSegmentation: boolean;
|
||||
installedTFAnnotation: boolean;
|
||||
modelsAreBeingFetched: boolean;
|
||||
modelsFetchingError: any;
|
||||
registeredUsers: any[];
|
||||
models: Model[];
|
||||
getModels(): void;
|
||||
deleteModel(id: number): void;
|
||||
}
|
||||
|
||||
export default function ModelsPageComponent(props: Props) {
|
||||
if (props.modelsAreBeingFetched) {
|
||||
props.getModels();
|
||||
return (
|
||||
<Spin size='large' style={{margin: '25% 45%'}}/>
|
||||
);
|
||||
} else {
|
||||
const uploadedModels = props.models.filter((model) => model.id !== null);
|
||||
const integratedModels = props.models.filter((model) => model.id === null);
|
||||
|
||||
return (
|
||||
<div className='cvat-models-page'>
|
||||
<TopBarComponent installedAutoAnnotation={props.installedAutoAnnotation}/>
|
||||
{ integratedModels.length ?
|
||||
<BuiltModelsList models={integratedModels}/> : null }
|
||||
{ uploadedModels.length &&
|
||||
<UploadedModelsList
|
||||
registeredUsers={props.registeredUsers}
|
||||
models={uploadedModels}
|
||||
deleteModel={props.deleteModel}
|
||||
/>
|
||||
} { props.installedAutoAnnotation &&
|
||||
!props.installedTFAnnotation &&
|
||||
!props.installedTFSegmentation &&
|
||||
<EmptyListComponent/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Button,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
type Props = {
|
||||
installedAutoAnnotation: boolean;
|
||||
} & RouteComponentProps;
|
||||
|
||||
function TopBarComponent(props: Props) {
|
||||
return (
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={11} lg={9} xl={8} xxl={7}>
|
||||
<Text className='cvat-title'>Models</Text>
|
||||
</Col>
|
||||
<Col
|
||||
md={{span: 11}}
|
||||
lg={{span: 9}}
|
||||
xl={{span: 8}}
|
||||
xxl={{span: 7}}
|
||||
>
|
||||
{ props.installedAutoAnnotation &&
|
||||
<Button size='large' id='cvat-create-model-button' type='primary' onClick={
|
||||
() => props.history.push('/models/create')
|
||||
}> Create new model </Button>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(TopBarComponent);
|
||||
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Tag,
|
||||
Select,
|
||||
Menu,
|
||||
Dropdown,
|
||||
Button,
|
||||
Icon,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import moment from 'moment';
|
||||
|
||||
import { Model } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
model: Model;
|
||||
owner: any;
|
||||
onDelete(): void;
|
||||
}
|
||||
|
||||
export default function UploadedModelItem(props: Props) {
|
||||
const subMenuIcon = () => (<img src='/assets/icon-sub-menu.svg'/>);
|
||||
|
||||
return (
|
||||
<Row className='cvat-models-list-item' type='flex'>
|
||||
<Col span={4} xxl={3}>
|
||||
<Tag color='purple'>OpenVINO</Tag>
|
||||
</Col>
|
||||
<Col span={6} xxl={7}>
|
||||
<Text className='cvat-black-color'>
|
||||
{props.model.name}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Text className='cvat-black-color'>
|
||||
{props.owner ? props.owner.username : 'undefined'}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Text className='cvat-black-color'>
|
||||
{moment(props.model.uploadDate).format('MMMM Do YYYY')}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col span={5}>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder='Supported labels'
|
||||
style={{width: '90%'}}
|
||||
value='Supported labels'
|
||||
>
|
||||
{props.model.labels.map(
|
||||
(label) => <Select.Option key={label}>
|
||||
{label}
|
||||
</Select.Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<Text className='cvat-black-color'>Actions</Text>
|
||||
<Dropdown overlay={
|
||||
<Menu subMenuCloseDelay={0.15} className='cvat-task-item-menu'>
|
||||
<Menu.Item onClick={() => {
|
||||
props.onDelete();
|
||||
}}key='delete'>Delete</Menu.Item>
|
||||
</Menu>
|
||||
}>
|
||||
<Icon className='cvat-task-item-menu-icon' component={subMenuIcon}/>
|
||||
</Dropdown>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
import UploadedModelItem from './uploaded-model-item';
|
||||
import { Model } from '../../reducers/interfaces';
|
||||
|
||||
interface Props {
|
||||
registeredUsers: any[];
|
||||
models: Model[];
|
||||
deleteModel(id: number): void;
|
||||
}
|
||||
|
||||
export default function UploadedModelsListComponent(props: Props) {
|
||||
const items = props.models.map((model) => {
|
||||
const owner = props.registeredUsers.filter((user) => user.id === model.ownerID)[0];
|
||||
return (
|
||||
<UploadedModelItem
|
||||
key={model.id as number}
|
||||
owner={owner}
|
||||
model={model}
|
||||
onDelete={() => props.deleteModel(model.id as number)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={22} lg={18} xl={16} xxl={14}>
|
||||
<Text className='cvat-black-color' strong>{'Uploaded by a user'}</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={22} lg={18} xl={16} xxl={14} className='cvat-models-list'>
|
||||
<Row type='flex' align='middle' style={{padding: '10px'}}>
|
||||
<Col span={4} xxl={3}>
|
||||
<Text strong>{'Framework'}</Text>
|
||||
</Col>
|
||||
<Col span={6} xxl={7}>
|
||||
<Text strong>{'Name'}</Text>
|
||||
</Col>
|
||||
<Col span={3}>
|
||||
<Text strong>Owner</Text>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Text strong>Uploaded</Text>
|
||||
</Col>
|
||||
<Col span={5}>
|
||||
<Text strong>Labels</Text>
|
||||
</Col>
|
||||
<Col span={2}/>
|
||||
</Row>
|
||||
{ items }
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import ActionsMenuComponent from '../../components/actions-menu/actions-menu';
|
||||
import { CombinedState } from '../../reducers/interfaces';
|
||||
import { showRunModelDialog } from '../../actions/models-actions';
|
||||
import {
|
||||
dumpAnnotationsAsync,
|
||||
loadAnnotationsAsync,
|
||||
deleteTaskAsync,
|
||||
} from '../../actions/tasks-actions';
|
||||
|
||||
interface OwnProps {
|
||||
taskInstance: any;
|
||||
}
|
||||
|
||||
interface StateToProps {
|
||||
loaders: any[];
|
||||
dumpers: any[];
|
||||
loadActivity: string | null;
|
||||
dumpActivities: string[] | null;
|
||||
installedTFAnnotation: boolean;
|
||||
installedTFSegmentation: boolean;
|
||||
installedAutoAnnotation: boolean;
|
||||
};
|
||||
|
||||
interface DispatchToProps {
|
||||
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => void;
|
||||
onDumpAnnotation: (taskInstance: any, dumper: any) => void;
|
||||
onDeleteTask: (taskInstance: any) => void;
|
||||
onOpenRunWindow: (taskInstance: any) => void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
|
||||
const { formats } = state;
|
||||
const { dumps } = state.tasks.activities;
|
||||
const { loads } = state.tasks.activities;
|
||||
const { plugins } = state.plugins;
|
||||
const id = own.taskInstance.id;
|
||||
|
||||
return {
|
||||
installedTFAnnotation: plugins.TF_ANNOTATION,
|
||||
installedTFSegmentation: plugins.TF_SEGMENTATION,
|
||||
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
|
||||
dumpActivities: dumps.byTask[id] ? dumps.byTask[id] : null,
|
||||
loadActivity: loads.byTask[id] ? loads.byTask[id] : null,
|
||||
loaders: formats.loaders,
|
||||
dumpers: formats.dumpers,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
onLoadAnnotation: (taskInstance: any, loader: any, file: File) => {
|
||||
dispatch(loadAnnotationsAsync(taskInstance, loader, file));
|
||||
},
|
||||
onDumpAnnotation: (taskInstance: any, dumper: any) => {
|
||||
dispatch(dumpAnnotationsAsync(taskInstance, dumper));
|
||||
},
|
||||
onDeleteTask: (taskInstance: any) => {
|
||||
dispatch(deleteTaskAsync(taskInstance));
|
||||
},
|
||||
onOpenRunWindow: (taskInstance: any) => {
|
||||
dispatch(showRunModelDialog(taskInstance));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function ActionsMenuContainer(props: OwnProps & StateToProps & DispatchToProps) {
|
||||
return (
|
||||
<ActionsMenuComponent
|
||||
taskInstance={props.taskInstance}
|
||||
loaders={props.loaders}
|
||||
dumpers={props.dumpers}
|
||||
loadActivity={props.loadActivity}
|
||||
dumpActivities={props.dumpActivities}
|
||||
installedTFAnnotation={props.installedTFAnnotation}
|
||||
installedTFSegmentation={props.installedTFSegmentation}
|
||||
installedAutoAnnotation={props.installedAutoAnnotation}
|
||||
onLoadAnnotation={props.onLoadAnnotation}
|
||||
onDumpAnnotation={props.onDumpAnnotation}
|
||||
onDeleteTask={props.onDeleteTask}
|
||||
onOpenRunWindow={props.onOpenRunWindow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(ActionsMenuContainer);
|
||||
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import CreateModelPageComponent from '../../components/create-model-page/create-model-page';
|
||||
import { createModelAsync } from '../../actions/models-actions';
|
||||
import {
|
||||
ModelFiles,
|
||||
CombinedState,
|
||||
} from '../../reducers/interfaces';
|
||||
|
||||
interface StateToProps {
|
||||
isAdmin: boolean;
|
||||
modelCreatingError: any;
|
||||
modelCreatingStatus: string;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
createModel(name: string, files: ModelFiles, global: boolean): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const { models} = state;
|
||||
|
||||
return {
|
||||
isAdmin: state.auth.user.isAdmin,
|
||||
modelCreatingError: models.creatingError,
|
||||
modelCreatingStatus: models.creatingStatus,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
createModel(name: string, files: ModelFiles, global: boolean) {
|
||||
dispatch(createModelAsync(name, files, global));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function CreateModelPageContainer(props: StateToProps & DispatchToProps) {
|
||||
return (
|
||||
<CreateModelPageComponent
|
||||
isAdmin={props.isAdmin}
|
||||
modelCreatingError={props.modelCreatingError ? props.modelCreatingError.toString() : ''}
|
||||
modelCreatingStatus={props.modelCreatingStatus}
|
||||
createModel={props.createModel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(CreateModelPageContainer);
|
||||
@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import ModelRunnerModalComponent from '../../components/model-runner-modal/model-runner-modal';
|
||||
import {
|
||||
Model,
|
||||
CombinedState,
|
||||
} from '../../reducers/interfaces';
|
||||
import {
|
||||
getModelsAsync,
|
||||
inferModelAsync,
|
||||
closeRunModelDialog,
|
||||
} from '../../actions/models-actions';
|
||||
|
||||
|
||||
interface StateToProps {
|
||||
startingError: any;
|
||||
modelsInitialized: boolean;
|
||||
models: Model[];
|
||||
activeProcesses: {
|
||||
[index: string]: string
|
||||
};
|
||||
taskInstance: any;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
inferModelAsync(
|
||||
taskInstance: any,
|
||||
model: Model,
|
||||
mapping: {
|
||||
[index: string]: string
|
||||
},
|
||||
cleanOut: boolean,
|
||||
): void;
|
||||
getModels(): void;
|
||||
closeDialog(): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const { models } = state;
|
||||
|
||||
return {
|
||||
modelsInitialized: models.initialized,
|
||||
models: models.models,
|
||||
activeProcesses: {},
|
||||
taskInstance: models.activeRunTask,
|
||||
visible: models.visibleRunWindows,
|
||||
startingError: models.startingError,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return ({
|
||||
inferModelAsync(
|
||||
taskInstance: any,
|
||||
model: Model,
|
||||
mapping: {
|
||||
[index: string]: string
|
||||
},
|
||||
cleanOut: boolean): void {
|
||||
dispatch(inferModelAsync(taskInstance, model, mapping, cleanOut));
|
||||
},
|
||||
getModels(): void {
|
||||
dispatch(getModelsAsync());
|
||||
},
|
||||
closeDialog(): void {
|
||||
dispatch(closeRunModelDialog());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function ModelRunnerModalContainer(props: StateToProps & DispatchToProps) {
|
||||
return (
|
||||
<ModelRunnerModalComponent
|
||||
modelsInitialized={props.modelsInitialized}
|
||||
models={props.models}
|
||||
activeProcesses={props.activeProcesses}
|
||||
visible={props.visible}
|
||||
taskInstance={props.taskInstance}
|
||||
getModels={props.getModels}
|
||||
closeDialog={props.closeDialog}
|
||||
runInference={props.inferModelAsync}
|
||||
startingError={props.startingError ? props.startingError.toString() : ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
) (ModelRunnerModalContainer);
|
||||
@ -1,13 +1,79 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
export default class ModelsPageContainer extends React.PureComponent {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div> Models page </div>
|
||||
)
|
||||
}
|
||||
}
|
||||
import ModelsPageComponent from '../../components/models-page/models-page';
|
||||
import {
|
||||
Model,
|
||||
CombinedState,
|
||||
} from '../../reducers/interfaces';
|
||||
import {
|
||||
getModelsAsync,
|
||||
deleteModelAsync,
|
||||
} from '../../actions/models-actions';
|
||||
|
||||
interface StateToProps {
|
||||
installedAutoAnnotation: boolean;
|
||||
installedTFAnnotation: boolean;
|
||||
installedTFSegmentation: boolean;
|
||||
modelsAreBeingFetched: boolean;
|
||||
modelsFetchingError: any;
|
||||
models: Model[];
|
||||
registeredUsers: any[];
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
getModels(): void;
|
||||
deleteModel(id: number): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const { plugins } = state.plugins;
|
||||
const { models } = state;
|
||||
|
||||
return {
|
||||
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
|
||||
installedTFAnnotation: plugins.TF_ANNOTATION,
|
||||
installedTFSegmentation: plugins.TF_SEGMENTATION,
|
||||
modelsAreBeingFetched: !models.initialized,
|
||||
modelsFetchingError: models.fetchingError,
|
||||
models: models.models,
|
||||
registeredUsers: state.users.users,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
getModels() {
|
||||
dispatch(getModelsAsync());
|
||||
},
|
||||
deleteModel(id: number) {
|
||||
dispatch(deleteModelAsync(id));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function ModelsPageContainer(props: DispatchToProps & StateToProps) {
|
||||
const render = props.installedAutoAnnotation
|
||||
|| props.installedTFAnnotation
|
||||
|| props.installedTFSegmentation;
|
||||
|
||||
return (
|
||||
render ?
|
||||
<ModelsPageComponent
|
||||
installedAutoAnnotation={props.installedAutoAnnotation}
|
||||
installedTFSegmentation={props.installedTFSegmentation}
|
||||
installedTFAnnotation={props.installedTFAnnotation}
|
||||
modelsAreBeingFetched={props.modelsAreBeingFetched}
|
||||
modelsFetchingError={props.modelsFetchingError}
|
||||
registeredUsers={props.registeredUsers}
|
||||
models={props.models}
|
||||
getModels={props.getModels}
|
||||
deleteModel={props.deleteModel}
|
||||
/> : null
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(ModelsPageContainer);
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
dumpAnnotationsAsync,
|
||||
loadAnnotationsAsync,
|
||||
deleteTaskAsync,
|
||||
} from '../../actions/tasks-actions';
|
||||
|
||||
import TopBarComponent from '../../components/task-page/top-bar';
|
||||
import { CombinedState } from '../../reducers/root-reducer';
|
||||
|
||||
interface StateToProps {
|
||||
taskInstance: any;
|
||||
loaders: any[];
|
||||
dumpers: any[];
|
||||
loadActivity: string | null;
|
||||
dumpActivities: string[] | null;
|
||||
installedTFAnnotation: boolean;
|
||||
installedAutoAnnotation: boolean;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
deleteTask: (taskInstance: any) => void;
|
||||
dumpAnnotations: (task: any, format: string) => void;
|
||||
loadAnnotations: (task: any, format: string, file: File) => void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const taskInstance = (state.activeTask.task as any).instance;
|
||||
|
||||
const { plugins } = state.plugins;
|
||||
const { formats } = state;
|
||||
const { dumps } = state.tasks.activities;
|
||||
const { loads } = state.tasks.activities;
|
||||
|
||||
const { id } = taskInstance;
|
||||
const dumpActivities = dumps.byTask[id] ? dumps.byTask[id] : null;
|
||||
const loadActivity = loads.byTask[id] ? loads.byTask[id] : null;
|
||||
|
||||
return {
|
||||
taskInstance,
|
||||
loaders: formats.loaders,
|
||||
dumpers: formats.dumpers,
|
||||
dumpActivities,
|
||||
loadActivity,
|
||||
installedTFAnnotation: plugins.TF_ANNOTATION,
|
||||
installedAutoAnnotation: plugins.AUTO_ANNOTATION,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
deleteTask: (taskInstance: any): void => {
|
||||
dispatch(deleteTaskAsync(taskInstance));
|
||||
},
|
||||
dumpAnnotations: (task: any, dumper: any): void => {
|
||||
dispatch(dumpAnnotationsAsync(task, dumper));
|
||||
},
|
||||
loadAnnotations: (task: any, loader: any, file: File): void => {
|
||||
dispatch(loadAnnotationsAsync(task, loader, file));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function TaskPageContainer(props: StateToProps & DispatchToProps) {
|
||||
return (
|
||||
<TopBarComponent
|
||||
taskInstance={props.taskInstance}
|
||||
loaders={props.loaders}
|
||||
dumpers={props.dumpers}
|
||||
loadActivity={props.loadActivity}
|
||||
dumpActivities={props.dumpActivities}
|
||||
installedTFAnnotation={props.installedTFAnnotation}
|
||||
installedAutoAnnotation={props.installedAutoAnnotation}
|
||||
onDeleteTask={props.deleteTask}
|
||||
onDumpAnnotation={props.dumpAnnotations}
|
||||
onLoadAnnotation={props.loadAnnotations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(TaskPageContainer);
|
||||
@ -0,0 +1,125 @@
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { ModelsActionTypes } from '../actions/models-actions';
|
||||
import { ModelsState } from './interfaces';
|
||||
|
||||
const defaultState: ModelsState = {
|
||||
initialized: false,
|
||||
creatingStatus: '',
|
||||
creatingError: null,
|
||||
startingError: null,
|
||||
fetchingError: null,
|
||||
deletingErrors: {},
|
||||
models: [],
|
||||
visibleRunWindows: false,
|
||||
activeRunTask: null,
|
||||
runnings: [],
|
||||
};
|
||||
|
||||
export default function (state = defaultState, action: AnyAction): ModelsState {
|
||||
switch (action.type) {
|
||||
case ModelsActionTypes.GET_MODELS: {
|
||||
return {
|
||||
...state,
|
||||
fetchingError: null,
|
||||
initialized: false,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.GET_MODELS_SUCCESS: {
|
||||
return {
|
||||
...state,
|
||||
models: action.payload.models,
|
||||
initialized: true,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.GET_MODELS_FAILED: {
|
||||
return {
|
||||
...state,
|
||||
fetchingError: action.payload.error,
|
||||
initialized: true,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.DELETE_MODEL: {
|
||||
const errors = { ...state.deletingErrors };
|
||||
delete errors[action.payload.id];
|
||||
return {
|
||||
...state,
|
||||
deletingErrors: errors,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.DELETE_MODEL_SUCCESS: {
|
||||
return {
|
||||
...state,
|
||||
models: state.models.filter(
|
||||
(model): boolean => model.id !== action.payload.id,
|
||||
),
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.DELETE_MODEL_FAILED: {
|
||||
const errors = { ...state.deletingErrors };
|
||||
errors[action.payload.id] = action.payload.error;
|
||||
return {
|
||||
...state,
|
||||
deletingErrors: errors,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.CREATE_MODEL: {
|
||||
return {
|
||||
...state,
|
||||
creatingError: null,
|
||||
creatingStatus: '',
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.CREATE_MODEL_STATUS_UPDATED: {
|
||||
return {
|
||||
...state,
|
||||
creatingStatus: action.payload.status,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.CREATE_MODEL_FAILED: {
|
||||
return {
|
||||
...state,
|
||||
creatingError: action.payload.error,
|
||||
creatingStatus: '',
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.CREATE_MODEL_SUCCESS: {
|
||||
return {
|
||||
...state,
|
||||
initialized: false,
|
||||
creatingStatus: 'CREATED',
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.INFER_MODEL: {
|
||||
return {
|
||||
...state,
|
||||
startingError: null,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.INFER_MODEL_FAILED: {
|
||||
return {
|
||||
...state,
|
||||
startingError: action.payload.error,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.SHOW_RUN_MODEL_DIALOG: {
|
||||
return {
|
||||
...state,
|
||||
visibleRunWindows: true,
|
||||
activeRunTask: action.payload.taskInstance,
|
||||
};
|
||||
}
|
||||
case ModelsActionTypes.CLOSE_RUN_MODEL_DIALOG: {
|
||||
return {
|
||||
...state,
|
||||
visibleRunWindows: false,
|
||||
activeRunTask: null,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { TaskActionTypes } from '../actions/task-actions';
|
||||
import { Task, TaskState } from './interfaces';
|
||||
|
||||
const defaultState: TaskState = {
|
||||
taskFetchingError: null,
|
||||
taskUpdatingError: null,
|
||||
task: null,
|
||||
};
|
||||
|
||||
export default function (state = defaultState, action: AnyAction): TaskState {
|
||||
switch (action.type) {
|
||||
case TaskActionTypes.GET_TASK:
|
||||
return {
|
||||
...state,
|
||||
taskFetchingError: null,
|
||||
taskUpdatingError: null,
|
||||
};
|
||||
case TaskActionTypes.GET_TASK_SUCCESS: {
|
||||
return {
|
||||
...state,
|
||||
task: {
|
||||
instance: action.payload.taskInstance,
|
||||
preview: action.payload.previewImage,
|
||||
},
|
||||
};
|
||||
}
|
||||
case TaskActionTypes.GET_TASK_FAILED: {
|
||||
return {
|
||||
...state,
|
||||
task: null,
|
||||
taskFetchingError: action.payload.error,
|
||||
};
|
||||
}
|
||||
case TaskActionTypes.UPDATE_TASK: {
|
||||
return {
|
||||
...state,
|
||||
taskUpdatingError: null,
|
||||
taskFetchingError: null,
|
||||
};
|
||||
}
|
||||
case TaskActionTypes.UPDATE_TASK_SUCCESS: {
|
||||
return {
|
||||
...state,
|
||||
task: {
|
||||
...(state.task as Task),
|
||||
instance: action.payload.taskInstance,
|
||||
},
|
||||
};
|
||||
}
|
||||
case TaskActionTypes.UPDATE_TASK_FAILED: {
|
||||
return {
|
||||
...state,
|
||||
task: {
|
||||
...(state.task as Task),
|
||||
instance: action.payload.taskInstance,
|
||||
},
|
||||
taskUpdatingError: action.payload.error,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return { ...state };
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,28 @@
|
||||
import thunk from 'redux-thunk';
|
||||
import { createStore, applyMiddleware, Store } from 'redux';
|
||||
|
||||
import createRootReducer from './reducers/root-reducer';
|
||||
import {
|
||||
createStore,
|
||||
applyMiddleware,
|
||||
Store,
|
||||
Reducer,
|
||||
} from 'redux';
|
||||
|
||||
const middlewares = [
|
||||
thunk,
|
||||
];
|
||||
|
||||
export default function createCVATStore(): Store {
|
||||
return createStore(
|
||||
let store: Store | null = null;
|
||||
|
||||
export default function createCVATStore(createRootReducer: () => Reducer): void {
|
||||
store = createStore(
|
||||
createRootReducer(),
|
||||
applyMiddleware(...middlewares),
|
||||
);
|
||||
}
|
||||
|
||||
export function getCVATStore(): Store {
|
||||
if (store) {
|
||||
return store;
|
||||
}
|
||||
|
||||
throw new Error('First create a store');
|
||||
}
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
import getCore from '../core';
|
||||
|
||||
const core = getCore();
|
||||
const baseURL = core.config.backendAPI.slice(0, -7);
|
||||
|
||||
interface GitPlugin {
|
||||
name: string;
|
||||
description: string;
|
||||
cvat: {
|
||||
classes: {
|
||||
Task: {
|
||||
prototype: {
|
||||
save: {
|
||||
leave: (plugin: GitPlugin, task: any) => void;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
data: {
|
||||
task: any;
|
||||
lfs: boolean;
|
||||
repos: string;
|
||||
};
|
||||
callbacks: {
|
||||
onStatusChange: ((status: string) => void) | null;
|
||||
};
|
||||
}
|
||||
|
||||
interface ReposData {
|
||||
url: string;
|
||||
status: {
|
||||
value: 'sync' | '!sync' | 'merged';
|
||||
error: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
function waitForClone(cloneResponse: any): Promise<void> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
async function checkCallback(): Promise<void> {
|
||||
core.server.request(
|
||||
`${baseURL}/git/repository/check/${cloneResponse.rq_id}`,
|
||||
{
|
||||
method: 'GET',
|
||||
},
|
||||
).then((response: any): void => {
|
||||
if (['queued', 'started'].includes(response.status)) {
|
||||
setTimeout(checkCallback, 1000);
|
||||
} else if (response.status === 'finished') {
|
||||
resolve();
|
||||
} else if (response.status === 'failed') {
|
||||
let message = 'Repository status check failed. ';
|
||||
if (response.stderr) {
|
||||
message += response.stderr;
|
||||
}
|
||||
|
||||
reject(message);
|
||||
} else {
|
||||
const message = `Repository status check returned the status "${response.status}"`;
|
||||
reject(message);
|
||||
}
|
||||
}).catch((error: any): void => {
|
||||
const message = `Can not sent a request to clone the repository. ${error.toString()}`;
|
||||
reject(message);
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(checkCallback, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
async function cloneRepository(
|
||||
this: any,
|
||||
plugin: GitPlugin,
|
||||
createdTask: any,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
if (typeof (this.id) !== 'undefined' || plugin.data.task !== this) {
|
||||
// not the first save, we do not need to clone the repository
|
||||
// or anchor set for another task
|
||||
resolve();
|
||||
} else if (plugin.data.repos) {
|
||||
if (plugin.callbacks.onStatusChange) {
|
||||
plugin.callbacks.onStatusChange('The repository is being cloned..');
|
||||
}
|
||||
|
||||
core.server.request(`${baseURL}/git/repository/create/${createdTask.id}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
data: JSON.stringify({
|
||||
path: plugin.data.repos,
|
||||
lfs: plugin.data.lfs,
|
||||
tid: createdTask.id,
|
||||
}),
|
||||
}).then(waitForClone).then((): void => {
|
||||
resolve();
|
||||
}).catch((error: any): void => {
|
||||
createdTask.delete().finally((): void => {
|
||||
reject(
|
||||
new core.exceptions.PluginError(
|
||||
typeof (error) === 'string'
|
||||
? error : error.message,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function registerGitPlugin(): void {
|
||||
const plugin: GitPlugin = {
|
||||
name: 'Git',
|
||||
description: 'Plugin allows to work with git repositories',
|
||||
cvat: {
|
||||
classes: {
|
||||
Task: {
|
||||
prototype: {
|
||||
save: {
|
||||
leave: cloneRepository,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
task: null,
|
||||
lfs: false,
|
||||
repos: '',
|
||||
},
|
||||
callbacks: {
|
||||
onStatusChange: null,
|
||||
},
|
||||
};
|
||||
|
||||
core.plugins.register(plugin);
|
||||
}
|
||||
|
||||
export async function getReposData(tid: number): Promise<ReposData | null> {
|
||||
const response = await core.server.request(
|
||||
`${baseURL}/git/repository/get/${tid}`,
|
||||
{
|
||||
method: 'GET',
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.url.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
url: response.url.value.split(/\s+/)[0],
|
||||
status: {
|
||||
value: response.status.value,
|
||||
error: response.status.error,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function syncRepos(tid: number): Promise<void> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
core.server.request(`${baseURL}/git/repository/push/${tid}`, {
|
||||
method: 'GET',
|
||||
}).then((syncResponse: any): void => {
|
||||
async function checkSync(): Promise<void> {
|
||||
const id = syncResponse.rq_id;
|
||||
const response = await core.server.request(`${baseURL}/git/repository/check/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
if (['queued', 'started'].includes(response.status)) {
|
||||
setTimeout(checkSync, 1000);
|
||||
} else if (response.status === 'finished') {
|
||||
resolve();
|
||||
} else if (response.status === 'failed') {
|
||||
const message = `Can not push to remote repository. Message: ${response.stderr}`;
|
||||
throw new Error(message);
|
||||
} else {
|
||||
const message = `Check returned status "${response.status}".`;
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(checkSync, 1000);
|
||||
}).catch((error: any): void => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue