This commit is contained in:
IDONTSUDO 2024-04-15 18:24:44 +03:00
parent c10cdb8158
commit 78ebb748ea
19 changed files with 2032 additions and 124 deletions

3
.prettierrc Normal file
View file

@ -0,0 +1,3 @@
{
"printWidth":120
}

1
p.ts
View file

@ -1 +0,0 @@

1873
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,13 +24,13 @@
"chai": "latest",
"eslint": "^8.47.0",
"mocha": "latest",
"node-watch": "^0.7.4",
"nodemon": "^3.0.1",
"nyc": "latest",
"source-map-support": "latest",
"ts-node": "^10.9.1",
"tslint": "latest",
"typescript": "^5.1.6",
"node-watch": "^0.7.4",
"nodemon": "^3.0.1"
"typescript": "^5.1.6"
},
"dependencies": {
"@grpc/grpc-js": "^1.9.0",
@ -45,13 +45,14 @@
"md5": "^2.3.0",
"mongoose": "^7.6.2",
"mongoose-autopopulate": "^1.1.0",
"pm2": "^5.3.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^5.0.5",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"spark-md5": "^3.0.2",
"ts-md5": "^1.3.1",
"tsc-watch": "^6.0.4",
"uuid": "^9.0.1",
"pm2": "^5.3.1"
"uuid": "^9.0.1"
}
}

View file

@ -1,5 +1,6 @@
import * as fs from "fs";
import { promisify } from "node:util";
import { rimraf } from 'rimraf'
export class FileSystemRepository {
public createDir = promisify(fs.mkdir);
@ -9,7 +10,7 @@ export class FileSystemRepository {
public stat = promisify(fs.stat);
public readFileAsync = promisify(fs.readFile);
public readdir = promisify(fs.readdir);
public deleteDirRecursive = rimraf;
async readFileAtBuffer(path: string): Promise<Buffer> {
if ((await this.lsStat(path)).isDirectory()) {
return (
@ -40,4 +41,5 @@ export class FileSystemRepository {
});
return filesToDir;
}
}

View file

@ -0,0 +1,12 @@
import { Result } from "../helpers/result";
import { FileSystemRepository } from "../repository/file_system_repository";
export class DeleteRecursiveFolderUseCase{
repository:FileSystemRepository = new FileSystemRepository()
call = async (path:string):Promise<Result<void,void>> =>{
console.log(path)
await this.repository.deleteDirRecursive(path)
return Result.ok()
}
}

View file

@ -1,6 +1,7 @@
import { CrudController } from "../../core/controllers/crud_controller";
import { IsHaveActiveProcessUseCase, KillLastProcessUseCase } from "../../core/usecases/exec_process_usecase";
import { CreateDataSetScenario } from "./domain/create_dataset_scenario";
import { DeleteDatasetUseCase } from "./domain/delete_dataset_use_case";
import { ExecDatasetProcessScenario } from "./domain/exec_process_scenario";
import { GetDatasetActiveProjectScenario } from "./domain/get_dataset_active_project_scenario";
import { DatasetDBModel } from "./models/dataset_database_model";
@ -15,6 +16,7 @@ export class DatasetsPresentation extends CrudController<DatasetValidationModel,
});
super.post(new CreateDataSetScenario().call);
super.get(new GetDatasetActiveProjectScenario().call);
super.delete(null)
this.subRoutes.push({
method: "POST",
subUrl: "exec",
@ -30,5 +32,11 @@ export class DatasetsPresentation extends CrudController<DatasetValidationModel,
subUrl: "delete/process",
fn: new KillLastProcessUseCase(),
});
this.subRoutes.push({
method: "DELETE",
subUrl: "dataset",
fn: new DeleteDatasetUseCase()
})
}
}

View file

@ -0,0 +1,19 @@
import { CallbackStrategyWithIdQuery, ResponseBase } from "../../../core/controllers/http_controller";
import { Result } from "../../../core/helpers/result";
import { DeleteDataBaseModelUseCase } from "../../../core/usecases/delete_database_model_usecase";
import { DeleteRecursiveFolderUseCase } from "../../../core/usecases/delete_recursive_folder_usecase";
import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase";
import { MongoIdValidation } from "../../../core/validations/mongo_id_validation";
import { DatasetDBModel } from "../models/dataset_database_model";
import { IDatasetModel } from "../models/dataset_validation_model";
export class DeleteDatasetUseCase extends CallbackStrategyWithIdQuery {
idValidationExpression = new MongoIdValidation();
call = async (id: string): ResponseBase =>
(await new ReadByIdDataBaseModelUseCase<IDatasetModel>(DatasetDBModel).call(id)).map(async (model) =>
(await new DeleteRecursiveFolderUseCase().call(`${model.local_path}/${model.name}/`)).map(async () =>
(await new DeleteDataBaseModelUseCase(model).call(model._id)).map(() => Result.ok({ status: "delete dataset" }))
)
);
}

View file

@ -6,16 +6,18 @@ import { MongoIdValidation } from "../../../core/validations/mongo_id_validation
import { DatasetDBModel } from "../models/dataset_database_model";
import { IDatasetModel } from "../models/dataset_validation_model";
import { ProcessWatcherAndDatabaseUpdateService } from "./create_dataset_scenario";
import { UpdateDataBaseModelUseCase } from "../../../core/usecases/update_database_model_usecase";
export class ExecDatasetProcessScenario extends CallbackStrategyWithIdQuery {
idValidationExpression = new MongoIdValidation();
call = async (id: string): ResponseBase => {
return (await new ReadByIdDataBaseModelUseCase<IDatasetModel>(DatasetDBModel).call(id)).map(async (model) => {
return (await new IsHaveActiveProcessUseCase().call()).map(() => {
return new ExecProcessUseCase().call(
return (await new IsHaveActiveProcessUseCase().call()).map(async () => {
await DatasetDBModel.findById(id).updateOne({processStatus:"RUN"})
return new ExecProcessUseCase().call(
`${model.project.rootDir}/`,
`python3 $PYTHON_BLENDER_PROC --cfg '${JSON.stringify(model)}' `,
`python3 $PYTHON_BLENDER_PROC --path '${model.project.rootDir}/${model.name}/'`,
new ProcessWatcherAndDatabaseUpdateService(id as unknown as ObjectId)
);
});

View file

@ -1,5 +1,5 @@
import { Type } from "class-transformer";
import { IsArray, IsString, ValidateNested } from "class-validator";
import { IsArray, IsOptional, IsString, ValidateNested } from "class-validator";
import { IProjectModel } from "../../_projects/models/project_database_model";
export class FormBuilderValidationModel {
@ -16,6 +16,7 @@ export enum ProcessStatus {
NEW = "NEW",
}
export interface IDatasetModel {
_id?:string;
name: string;
local_path: string;
dataSetObjects: string[];
@ -34,6 +35,8 @@ export class DatasetValidationModel implements IDatasetModel {
@Type(() => FormBuilderValidationModel)
public formBuilder: FormBuilderValidationModel;
public local_path: string;
@IsOptional()
@IsString()
public processStatus: ProcessStatus;
public projectId: string;
public processLogs: string;

View file

@ -4,6 +4,8 @@ import { Result } from "../helper/result";
export enum HttpMethod {
GET = "GET",
POST = "POST",
DELETE = "DELETE",
PUT = "PUT"
}
export class HttpError extends Error {
status: number;

View file

@ -6,13 +6,14 @@ export interface IButtonProps {
filled?: boolean;
text?: string;
onClick?: any;
style?:React.CSSProperties
}
export function CoreButton(props: IButtonProps) {
return (
<div
onClick={() => props.onClick?.call()}
style={{
style={Object.assign({
backgroundColor: props.filled ? "rgba(103, 80, 164, 1)" : "",
paddingRight: 20,
paddingLeft: 20,
@ -20,7 +21,7 @@ export function CoreButton(props: IButtonProps) {
paddingBottom: 10,
borderRadius: 24,
border: props.block ? "1px solid rgba(29, 27, 32, 0.12)" : props.filled ? "" : "1px solid black",
}}
},props.style)}
>
<CoreText
text={props.text ?? ""}

View file

@ -54,13 +54,15 @@ export class FormViewModel {
this.inputs = inputs;
makeAutoObservable(this);
}
public json(){
return JSON.parse(this.toResult().replaceAll("\n", "").replaceAll("\\", "").replaceAll("/", ""))
}
public fromFormBuilderValidationModel() {
return new FormBuilderValidationModel(
this.context,
this.result,
this.inputs.map((el) => el.toJson()),
JSON.parse(this.toResult().replaceAll("\n", "").replaceAll("\\", "").replaceAll("/", ""))
this.json() as any
);
}
public toResult(): string {

View file

@ -1,5 +1,6 @@
import React from "react";
import { CoreText, CoreTextType } from "../text/text";
import { Input } from "antd";
interface IInputProps {
label: string;
@ -9,18 +10,16 @@ interface IInputProps {
validation?: (value: string) => boolean;
error?: string;
}
export const CoreInput = (props: IInputProps) => {
const [value, setValue] = React.useState<string>(() => props.value ?? "");
const ref = React.useRef<HTMLDivElement>(null);
const [isAppendInnerText, setAppendInnerText] = React.useState(true);
React.useEffect(() => {
if (ref.current && isAppendInnerText) {
ref.current.innerText = value;
setAppendInnerText(false);
}
}, [ref, value, isAppendInnerText, setAppendInnerText]);
const [key, setKey] = React.useState<undefined | string>(undefined);
React.useEffect(() => {
setKey(props.value);
return () => {
setKey(undefined);
};
}, [key, setKey, props]);
return (
<div
style={Object.assign(
@ -36,28 +35,36 @@ export const CoreInput = (props: IInputProps) => {
>
<CoreText type={CoreTextType.small} text={props.label} />
<div
ref={ref}
onInput={(e) => {
if (e.currentTarget.textContent) {
setValue(e.currentTarget.textContent);
if (props.validation !== undefined && props.validation(value) && props.onChange) {
props.onChange(value);
<Input
key={key}
defaultValue={props.value}
style={{
backgroundColor: "#00008000",
border: 1,
fontSize: 16,
fontFamily: "Roboto",
color: "#1D1B20",
height: 24,
width: "100%",
}}
onChange={(e) => {
const val = e.target.value;
if (val) {
if (props.validation !== undefined && props.validation(val) && props.onChange) {
props.onChange(val);
return;
}
if (props.onChange && props.validation === undefined) {
props.onChange(value);
props.onChange(val);
return;
}
}
}}
style={{ fontSize: 16, fontFamily: "Roboto", color: "#1D1B20", height: 24 }}
contentEditable="true"
/>
{value ? (
{props.value ? (
props.validation ? (
props.validation(value) ? null : (
props.validation(props.value) ? null : (
<div style={{ color: "#ff1d0c" }}>{props.error ? props.error : "error"}</div>
)
) : null

View file

@ -34,13 +34,24 @@ interface ICardDataSetProps {
export const CardDataSet = (props: ICardDataSetProps) => {
const menu: IMenuItem[] = [
{ onClick: () => props.onEdit?.call(props.id), name: "Редактировать" },
{ onClick: () => props.onDelete?.call(props.id), name: "Удалить" },
{
onClick: () => {
if (props.onEdit) props.onEdit(props.id);
},
name: "Редактировать",
},
{
onClick: () => {
if (props.onDelete) props.onDelete(props.id);
},
name: "Удалить",
},
];
const items: MenuProps["items"] = menu.map((el, index) => {
return {
key: String(index),
label: <CoreText text={el.name} type={CoreTextType.medium} />,
onClick: () => el.onClick(props.id),
};
});
return (
@ -95,7 +106,7 @@ export const CardDataSet = (props: ICardDataSetProps) => {
<CoreText text={props.objects?.join(", ") ?? ""} type={CoreTextType.medium} color="#49454F" />
</div>
<div style={{ display: "flex", flexDirection: "row", justifyContent: "flex-end", alignItems: "center" }}>
{props.processStatus === "exec" ? (
{props.processStatus === ProcessStatus.RUN ? (
<Spin indicator={<LoadingOutlined style={{ fontSize: 34, color: "rgba(103, 80, 164, 1)" }} spin />} />
) : null}
<div style={{ width: 20 }} />
@ -110,9 +121,33 @@ export const CardDataSet = (props: ICardDataSetProps) => {
text="Старт"
/>
) : null}
{props.processStatus === ProcessStatus.END ? ( <CoreButton
onClick={() => {
if (props.type.isEqual(CardDataSetType.COMPLETED) && props.onClickButton && props.id) {
props.onClickButton(props.id);
}
}}
text="Завершен"
/>):null}
{props.processStatus === ProcessStatus.RUN ? (<>
<CoreButton
onClick={() => {
if (props.type.isEqual(CardDataSetType.COMPLETED) && props.onClickButton && props.id) {
props.onClickButton(props.id);
}
}}
block={true}
text="Стоп"
/>
</>):null}
{props.processStatus === ProcessStatus.ERROR ? (
<CoreButton
style={{
backgroundColor: "red",
}}
onClick={() => {
if (props.type.isEqual(CardDataSetType.COMPLETED) && props.onClickButton && props.id) {
props.onClickButton(props.id);

View file

@ -1,10 +1,12 @@
import { InputBuilderViewModel } from "../../core/ui/form_builder/form_view_model";
import { Result } from "../../core/helper/result";
import makeAutoObservable from "mobx-store-inheritance";
export enum ProcessStatus {
END = "END",
ERROR = "ERROR",
NEW = "NEW",
RUN = "RUN",
}
export interface IDatasetModel {
@ -48,7 +50,7 @@ export class FormBuilderValidationModel {
public result: string;
public context: string;
public form: string[];
public output: string;
public output: any;
constructor(context: string, result: string, form: string[], output: string) {
this.context = context;
this.result = result;
@ -56,7 +58,7 @@ export class FormBuilderValidationModel {
this.output = output;
}
static empty() {
return new FormBuilderValidationModel(datasetFormMockContext, datasetFormMockResult, [], "");
return new FormBuilderValidationModel(datasetFormMockContext, datasetFormMockResult, [], defaultFormValue);
}
}
@ -66,18 +68,24 @@ export class DataSetModel {
name: string;
formBuilder: FormBuilderValidationModel = FormBuilderValidationModel.empty();
project?: string;
constructor(dataSetObjects: string[], datasetType = datasetTypes[0], datasetName: string) {
processStatus?:string
isNew:boolean;
_id?:string;
constructor(dataSetObjects: string[], datasetType = datasetTypes[0], datasetName: string, isNew = true, id:string | undefined = undefined) {
this.dataSetObjects = dataSetObjects;
this.datasetType = datasetType;
this.name = datasetName;
this.isNew = isNew;
this._id = id;
makeAutoObservable(this);
}
static empty() {
return new DataSetModel([], "", "");
return new DataSetModel([], "", "", true);
}
isValid(): Result<string, void> {
if (this.project === undefined) {
return Result.error("project is unknow");
}
@ -92,6 +100,10 @@ export class DataSetModel {
}
return Result.ok();
}
static fromIDatasetModel(model: IDatasetModel) {
return new DataSetModel(model.dataSetObjects, model.datasetType, model.name, false, model._id);
}
}
export const datasetTypes = ["Object Detection - YOLOv8", "Pose Estimation - DOPE"];
@ -162,3 +174,11 @@ type LIGHTS = {
"energy_range":[\${ENERGY_RANGE_1:number:400},\${ENERGY_RANGE_2:number:900}]
};
`;
export const defaultFormValue: any = {
typedataset: "PoseEstimation",
models_randomization: { loc_range_low: [-1, -1, 0], loc_range_high: [1, 1, 2] },
scene: { objects: [], lights: [] },
camera_position: { center_shell: [0, 0, 0], radius_range: [1, 1.4], elevation_range: [10, 90] },
generation: { n_cam_pose: 5, n_sample_on_pose: 3, n_series: 100, image_format: "jpg", image_size_wh: [640, 480] },
};

View file

@ -1,9 +1,16 @@
import { Result } from "../../core/helper/result";
import { HttpError, HttpMethod, HttpRepository } from "../../core/repository/http_repository";
import { UUID } from "../all_projects/data/project_repository";
import { Assets, DataSetModel, Dataset, IDatasetModel } from "./dataset_model";
import { Assets, DataSetModel, Dataset, IDatasetModel, ProcessStatus } from "./dataset_model";
export class DataSetRepository extends HttpRepository {
editDataset(dataSetModel: DataSetModel) {
dataSetModel.processStatus = ProcessStatus.NEW;
return this._jsonRequest<void>(HttpMethod.PUT, `/datasets`, dataSetModel);
}
deleteDataset(id: string) {
return this._jsonRequest<void>(HttpMethod.DELETE, `/datasets/dataset?id=${id}`);
}
getActiveProjectId(): Promise<Result<HttpError, UUID>> {
return this._jsonRequest<UUID>(HttpMethod.GET, "/projects/get/active/project/id");
}

View file

@ -12,6 +12,8 @@ import { datasetTypes } from "./dataset_model";
import { CoreText, CoreTextType } from "../../core/ui/text/text";
export const DatasetsScreenPath = "/dataset";
export const DataSetScreen: React.FunctionComponent = observer(() => {
const [store] = React.useState(() => new DataSetStore());
@ -55,9 +57,9 @@ export const DataSetScreen: React.FunctionComponent = observer(() => {
<CardDataSet
type={CardDataSetType.EMPTY}
onClickEmptyCard={() => {
store.editDrawer(DrawersDataset.NewDataset, true);
store.openEmptyCard()
}}
/>
/>ч
<Drawer
title={DrawersDataset.FormBuilderDrawer}
onClose={() => store.editDrawer(DrawersDataset.FormBuilderDrawer, false)}
@ -67,11 +69,15 @@ export const DataSetScreen: React.FunctionComponent = observer(() => {
context={store.dataSetModel.formBuilder.context}
result={store.dataSetModel.formBuilder.result}
onChange={(el) => {
if (el) store.dataSetModel.formBuilder = el;
store.dataSetModel.formBuilder = el;
}}
/>
<div style={{ display: "flex" }}>
<CoreButton text="Сохранить" filled={true} />
<CoreButton
text="Сохранить"
filled={true}
onClick={() => store.editDrawer(DrawersDataset.FormBuilderDrawer, false)}
/>
<div style={{ width: 10 }} />
<CoreButton text="Отмена" onClick={() => store.editDrawer(DrawersDataset.FormBuilderDrawer, false)} />
</div>
@ -80,12 +86,12 @@ export const DataSetScreen: React.FunctionComponent = observer(() => {
title={DrawersDataset.NewDataset}
onClose={() => store.editDrawer(DrawersDataset.NewDataset, false)}
open={store.drawers.find((el) => el.name === DrawersDataset.NewDataset)?.status}
>
>
<div
style={{ display: "flex", flexDirection: "column", justifyContent: "space-between", height: "100%" }}
>
<div>
<CoreInput label={"Имя датасета"} onChange={(e) => store.setNewDatasetName(e)} />
<CoreInput value={store.dataSetModel.name} label={"Имя датасета"} onChange={(e) => store.setNewDatasetName(e)} />
<div>
<CoreText type={CoreTextType.header} text="Тип навыка" />
{datasetTypes.map((el) => {

View file

@ -34,6 +34,11 @@ export class DataSetStore extends UiErrorState<HttpError> {
makeAutoObservable(this);
}
openEmptyCard() {
this.dataSetModel = DataSetModel.empty();
this.editDrawer(DrawersDataset.NewDataset, true);
}
setNewDatasetName(e: string): void {
this.dataSetModel.name = e;
}
@ -66,9 +71,22 @@ export class DataSetStore extends UiErrorState<HttpError> {
return el;
});
}
editDataset(id: string) {}
deleteDataset(id: string) {}
editDataset(id: string) {
this.dataSetModel = DataSetModel.fromIDatasetModel(this.datasets?.find((el) => el._id === id) as IDatasetModel);
console.log(this.dataSetModel.name);
this.editDrawer(DrawersDataset.NewDataset, true);
}
deleteDataset = async (id: string) => {
(await this.dataSetRepository.deleteDataset(id)).fold(
async () => {
message.success("датасет удален");
await this.getDatasets();
},
async (e) => message.error(e.message)
);
};
runProcess = async (id: string): Promise<void> => {
(await this.dataSetRepository.isRunningProcess()).fold(
@ -91,12 +109,23 @@ export class DataSetStore extends UiErrorState<HttpError> {
this.dataSetModel.project = this.activeProject.id;
this.dataSetModel.isValid().fold(
async () => {
(await this.dataSetRepository.saveDataSet(this.dataSetModel)).fold(
() => {
message.success("Датасет сохранен");
},
(error) => message.error(error.message)
);
if (this.dataSetModel.isNew) {
(await this.dataSetRepository.saveDataSet(this.dataSetModel)).fold(
async () => {
message.success("Датасет сохранен");
await this.getDatasets();
},
async (error) => message.error(error.message)
);
} else {
(await this.dataSetRepository.editDataset(this.dataSetModel)).fold(
async () => {
message.success("Настройки датасета измнены");
await this.getDatasets();
},
async (error) => message.error(error.message)
);
}
},
async (error) => message.error(error)
);
@ -104,7 +133,10 @@ export class DataSetStore extends UiErrorState<HttpError> {
init = async () => {
await this.mapOk("assets", this.dataSetRepository.getAssetsActiveProject());
await this.mapOk("datasets", this.dataSetRepository.getDatasetsActiveProject());
await this.getDatasets();
await this.mapOk("activeProject", this.dataSetRepository.getActiveProjectId());
};
getDatasets = async () => {
await this.mapOk("datasets", this.dataSetRepository.getDatasetsActiveProject());
};
}