merge master into RTL branch (#647)
* merge master into RTL branch * fix an API icon
This commit is contained in:
Родитель
b6557e864c
Коммит
34627adaa0
|
@ -3,3 +3,4 @@
|
||||||
# without it, you'll see error like this
|
# without it, you'll see error like this
|
||||||
# Failed to load resource: net::ERR_FILE_NOT_FOUND /favicon.ico:1
|
# Failed to load resource: net::ERR_FILE_NOT_FOUND /favicon.ico:1
|
||||||
PUBLIC_URL=
|
PUBLIC_URL=
|
||||||
|
BROWSER=none
|
38
CHANGELOG.md
38
CHANGELOG.md
|
@ -1,8 +1,44 @@
|
||||||
# FoTT Changelog
|
# FoTT Changelog
|
||||||
|
## What's new in Form Recognizer?
|
||||||
|
Click [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/whats-new) to see what's new in Form Recognizer.
|
||||||
|
|
||||||
## Released conatiner's currently referenced commit
|
## Released conatiner's currently referenced commit
|
||||||
2.1-Preview's released container image (mcr.microsoft.com/azure-cognitive-services/custom-form/labeltool:2.1.012970002-amd64-preview) currently references **2.1-preview.1-0633507 (09-14-2020)**
|
2.1-Preview's released container image, tracked by the `latest-preview` image tag in our [docker hub repository](https://hub.docker.com/_/microsoft-azure-cognitive-services-custom-form-labeltool), currently references **2.1-preview.1-1f33130 (10-09-2020)**
|
||||||
|
|
||||||
## Commit history
|
## Commit history
|
||||||
|
### 2.1-preview.1-1f33130 (10-09-2020)
|
||||||
|
* fix: support image map interactions for container releases([#639](https://github.com/microsoft/OCR-Form-Tools/commit/1f33130e3b6ad8a876f18fc1c05f82c4a14d36fa))
|
||||||
|
|
||||||
|
### 2.1-preview.1-6d4e93b (10-07-2020)
|
||||||
|
* Fix: use file type library for mime type validation ([#636](https://github.com/microsoft/OCR-Form-Tools/commit/6d4e93bca8a4e3d677c765ed5596bde502766e2e))
|
||||||
|
|
||||||
|
### 2.1-preview.1-355ca0b (09-30-2020)
|
||||||
|
* feat: add spinner in saving project, can avoid multiple commit ([#617](https://github.com/microsoft/OCR-Form-Tools/commit/355ca0b156b2d44aafd2eaaccf2fc52385c7f5f8))
|
||||||
|
|
||||||
|
### 2.1-preview.1-53044f7 (09-29-2020)
|
||||||
|
* fix: refresh currentProjects when load project ([#615](https://github.com/microsoft/OCR-Form-Tools/commit/53044f72dd9c9c72557c74c00605ba05ee50205d))
|
||||||
|
* sync related region color when tag color changed ([#598](https://github.com/microsoft/OCR-Form-Tools/commit/3044cc51a9166877bb4f01f28753171b82c04ccd))
|
||||||
|
* feat: add current list item style ([#601](https://github.com/microsoft/OCR-Form-Tools/commit/3e503e75513e44e6a90bd013d8dd15c3096cd7e9))
|
||||||
|
* fix: remove project from app if security token does not exist ([#468](https://github.com/microsoft/OCR-Form-Tools/commit/730e1963a06f038a4efa9750fcef4be6f15a8460))
|
||||||
|
|
||||||
|
### 2.1-preview.1-d859d38 (09-27-2020)
|
||||||
|
* fix ,update document state when preview (#317) ([#471](https://github.com/microsoft/OCR-Form-Tools/commit/d859d38ecc1f96b194ffa130a1840f5a7d9b1a9b))
|
||||||
|
* refactor: change the confidence value format to percentage ([#461](https://github.com/microsoft/OCR-Form-Tools/commit/e806b4e0dfcc68e6408e2130a46a318637a482a8))
|
||||||
|
|
||||||
|
### 2.1-preview.1-7a3f7a7 (09-25-2020)
|
||||||
|
* security: upgrade node-forge ([#622](https://github.com/microsoft/OCR-Form-Tools/commit/7a3f7a773c8b01f443afaad89d7974a5bbb0b869))
|
||||||
|
* fix: disable move tag and support renaming when searching ([#618](https://github.com/microsoft/OCR-Form-Tools/commit/cac1e8e6cfb2805a6540f9e80d564a0ff8be81c7))
|
||||||
|
|
||||||
|
### 2.1-preview.1-4163edc (09-23-2020)
|
||||||
|
* docs: add latest tag reference to changelog ([#608](https://github.com/microsoft/OCR-Form-Tools/commit/4163edc18bc65234e263703fc829d2f297953385))
|
||||||
|
* fix: use region instead of drawnRegion for labelType in label file ([#582](https://github.com/microsoft/OCR-Form-Tools/commit/ffafc200249a1c47698fedb279b4b55cef0190ba))
|
||||||
|
* docs: update readme with docker hub info ([#604](https://github.com/microsoft/OCR-Form-Tools/commit/63bbea076d598d0286095fa0eca48d8c9d0ed706))
|
||||||
|
* fix: remove opening browser for yarn start ([#605](https://github.com/microsoft/OCR-Form-Tools/commit/f6c4dc3585df71d09252a28f65e835a594389118))
|
||||||
|
* fix: update changelog updater script ([#607](https://github.com/microsoft/OCR-Form-Tools/commit/7c4848c3a72259562c0461f0e2eadfb4a660fa64))
|
||||||
|
|
||||||
|
### 2.1-preview.1-f2db74e (09-17-2020)
|
||||||
|
* docs: udpate changlog with docker image reference ([#590](https://github.com/microsoft/OCR-Form-Tools/commit/f2db74e322c32338eba3b2df06c01a51cfb7ebc1))
|
||||||
|
|
||||||
### 2.1-preview.1-1a6b78e (09-16-2020)
|
### 2.1-preview.1-1a6b78e (09-16-2020)
|
||||||
* fix: normalize folder path starting with a period ([#592](https://github.com/microsoft/OCR-Form-Tools/commit/1a6b78e054235da3188aafbe65636a8c18b439bf))
|
* fix: normalize folder path starting with a period ([#592](https://github.com/microsoft/OCR-Form-Tools/commit/1a6b78e054235da3188aafbe65636a8c18b439bf))
|
||||||
* fix: change label folder uri title ([#588](https://github.com/microsoft/OCR-Form-Tools/commit/7e4233e568d94817e23dda5ef5513b9ee7475d11))
|
* fix: change label folder uri title ([#588](https://github.com/microsoft/OCR-Form-Tools/commit/7e4233e568d94817e23dda5ef5513b9ee7475d11))
|
||||||
|
|
|
@ -38,7 +38,7 @@ Form Labeling Tool requires [NodeJS (>= 10.x, Dubnium) and NPM](https://github.c
|
||||||
|
|
||||||
### Set up this tool with Docker
|
### Set up this tool with Docker
|
||||||
|
|
||||||
Please see instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/label-tool#set-up-the-sample-labeling-tool)
|
Please see instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/form-recognizer/quickstarts/label-tool#set-up-the-sample-labeling-tool), and view our docker hub repository [here](https://hub.docker.com/_/microsoft-azure-cognitive-services-custom-form-labeltool?tab=description) for the latest container image info. The `latest-preview` and `latest` docker image tags track the preview and general availability releases of FOTT.
|
||||||
|
|
||||||
### Run as web application
|
### Run as web application
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
"yarn": "^1.22.4"
|
"yarn": "^1.22.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "nf start -p 3000",
|
"start": "env-cmd -f .env.electron nf start -p 3000",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"react-start": "react-scripts start",
|
"react-start": "react-scripts start",
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"electron:start:dev": "yarn electron-start",
|
"electron:start:dev": "yarn electron-start",
|
||||||
"electron:start:prod": "yarn webpack:prod && yarn electron-start",
|
"electron:start:prod": "yarn webpack:prod && yarn electron-start",
|
||||||
"electron-start": "node src/electron/start",
|
"electron-start": "node src/electron/start",
|
||||||
"release": "env-cmd -f .env.release yarn build && yarn webpack:prod && yarn electron-builder",
|
"release": "env-cmd -f .env.electron yarn build && yarn webpack:prod && yarn electron-builder",
|
||||||
"tslint": "./node_modules/.bin/tslint 'src/**/*.ts*'",
|
"tslint": "./node_modules/.bin/tslint 'src/**/*.ts*'",
|
||||||
"tslintfix": "./node_modules/.bin/tslint 'src/**/*.ts*' --fix"
|
"tslintfix": "./node_modules/.bin/tslint 'src/**/*.ts*' --fix"
|
||||||
},
|
},
|
||||||
|
@ -108,7 +108,9 @@
|
||||||
"foreman": "^3.0.1",
|
"foreman": "^3.0.1",
|
||||||
"jquery": "^3.5.0",
|
"jquery": "^3.5.0",
|
||||||
"kind-of": "^6.0.3",
|
"kind-of": "^6.0.3",
|
||||||
|
"mime": "^2.4.6",
|
||||||
"minimist": "^1.2.2",
|
"minimist": "^1.2.2",
|
||||||
|
"node-forge": "^0.10.0",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"pdfjs-dist": "^2.4.456",
|
"pdfjs-dist": "^2.4.456",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
|
|
|
@ -37,15 +37,15 @@ repo = git.Repo("../")
|
||||||
commits = list(repo.iter_commits("master"))
|
commits = list(repo.iter_commits("master"))
|
||||||
for commit in commits:
|
for commit in commits:
|
||||||
commitHex = commit.hexsha[:7]
|
commitHex = commit.hexsha[:7]
|
||||||
if commitHex == lastChanglogCommit:
|
|
||||||
print("found last change log commit")
|
|
||||||
break
|
|
||||||
commitDate = commit.committed_datetime.strftime("%m-%d-%Y")
|
commitDate = commit.committed_datetime.strftime("%m-%d-%Y")
|
||||||
if currentCommitDate != commitDate:
|
if currentCommitDate != commitDate:
|
||||||
if currentCommitDate is not None:
|
if currentCommitDate is not None:
|
||||||
insterIntoChanglogContents("\n")
|
insterIntoChanglogContents("\n")
|
||||||
currentCommitDate = commitDate
|
currentCommitDate = commitDate
|
||||||
insterIntoChanglogContents("### " + appVersion + "-" + commitHex + " (" + commitDate + ")\n")
|
insterIntoChanglogContents("### " + appVersion + "-" + commitHex + " (" + commitDate + ")\n")
|
||||||
|
if commitHex == lastChanglogCommit:
|
||||||
|
print("found last change log commit")
|
||||||
|
break
|
||||||
commitMessage = commit.message.partition('\n')[0]
|
commitMessage = commit.message.partition('\n')[0]
|
||||||
commitMessageRegex = re.compile("(.*)\(\#(\d+)\)\s*$")
|
commitMessageRegex = re.compile("(.*)\(\#(\d+)\)\s*$")
|
||||||
match = commitMessageRegex.search(commitMessage)
|
match = commitMessageRegex.search(commitMessage)
|
||||||
|
|
|
@ -95,7 +95,7 @@ export default class App extends React.Component<IAppProps> {
|
||||||
<Sidebar project={this.props.currentProject} />
|
<Sidebar project={this.props.currentProject} />
|
||||||
<MainContentRouter />
|
<MainContentRouter />
|
||||||
</div>
|
</div>
|
||||||
<StatusBar>
|
<StatusBar project={this.props.currentProject} >
|
||||||
<StatusBarMetrics project={this.props.currentProject} />
|
<StatusBarMetrics project={this.props.currentProject} />
|
||||||
</StatusBar>
|
</StatusBar>
|
||||||
<ToastContainer className="frtt-toast-container" role="alert" />
|
<ToastContainer className="frtt-toast-container" role="alert" />
|
||||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
import { appInfo } from "./appInfo"
|
import { appInfo } from "./appInfo"
|
||||||
|
|
||||||
const appVersionArr = appInfo.version.split(".");
|
const appVersionRaw = appInfo.version
|
||||||
|
const appVersionArr = appVersionRaw.split(".");
|
||||||
appVersionArr[1] = appVersionArr[1] + "-preview";
|
appVersionArr[1] = appVersionArr[1] + "-preview";
|
||||||
const appVersion = appVersionArr.join(".");
|
const appVersion = appVersionArr.join(".");
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ const apiVersion = "v2.1-preview.1";
|
||||||
*/
|
*/
|
||||||
export const constants = {
|
export const constants = {
|
||||||
version: "pubpreview_1.0",
|
version: "pubpreview_1.0",
|
||||||
|
appVersionRaw,
|
||||||
appVersion,
|
appVersion,
|
||||||
apiVersion,
|
apiVersion,
|
||||||
projectFormTempKey: "projectForm",
|
projectFormTempKey: "projectForm",
|
||||||
|
@ -35,6 +37,7 @@ export const constants = {
|
||||||
convertedThumbnailQuality: 0.2,
|
convertedThumbnailQuality: 0.2,
|
||||||
recentModelRecordsCount: 5,
|
recentModelRecordsCount: 5,
|
||||||
apiModelsPath: `/formrecognizer/${apiVersion}/custom/models`,
|
apiModelsPath: `/formrecognizer/${apiVersion}/custom/models`,
|
||||||
|
autoLabelBatchSize: 10,
|
||||||
|
|
||||||
pdfjsWorkerSrc(version: string) {
|
pdfjsWorkerSrc(version: string) {
|
||||||
return `https://fotts.azureedge.net/npm/pdfjs-dist/${version}/pdf.worker.js`;
|
return `https://fotts.azureedge.net/npm/pdfjs-dist/${version}/pdf.worker.js`;
|
||||||
|
|
|
@ -129,6 +129,10 @@ export const english: IAppStrings = {
|
||||||
backEndNotAvailable: "Checkbox feature will work in future version of Form Recognizer service, please stay tuned.",
|
backEndNotAvailable: "Checkbox feature will work in future version of Form Recognizer service, please stay tuned.",
|
||||||
addName: "Add a model name...",
|
addName: "Add a model name...",
|
||||||
downloadJson: "Download JSON file",
|
downloadJson: "Download JSON file",
|
||||||
|
trainConfirm: {
|
||||||
|
title: "Labels not revised yet",
|
||||||
|
message: "You have label files not yet revised, do you want to train with those files?"
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
electron: {
|
electron: {
|
||||||
cantAccessFiles: "Cannot access files in '${folderUri}' for training. Please check if specified folder URI is correct."
|
cantAccessFiles: "Cannot access files in '${folderUri}' for training. Please check if specified folder URI is correct."
|
||||||
|
@ -209,7 +213,7 @@ export const english: IAppStrings = {
|
||||||
defaultURLInput: "Paste or type URL...",
|
defaultURLInput: "Paste or type URL...",
|
||||||
editAndUploadToTrainingSet: "Edit & upload to training set",
|
editAndUploadToTrainingSet: "Edit & upload to training set",
|
||||||
editAndUploadToTrainingSetNotify: "by clicking on this button, this form will be added to this project, where you can edit these labels.",
|
editAndUploadToTrainingSetNotify: "by clicking on this button, this form will be added to this project, where you can edit these labels.",
|
||||||
editAndUploadToTrainingSetNotify2: "We are adding this file to your training set, where you could edit the labels and re-train the model.",
|
editAndUploadToTrainingSetNotify2: "We are adding this file to your training set, where you can edit the labels and re-train the model.",
|
||||||
uploadInPrgoress: "Upload in progress...",
|
uploadInPrgoress: "Upload in progress...",
|
||||||
confirmDuplicatedAssetName: {
|
confirmDuplicatedAssetName: {
|
||||||
title: "Asset name exists",
|
title: "Asset name exists",
|
||||||
|
@ -264,7 +268,7 @@ export const english: IAppStrings = {
|
||||||
unknownTagName: "Unknown",
|
unknownTagName: "Unknown",
|
||||||
notCompatibleTagType: "Tag type is not compatible with this feature. If you want to change type of this tag, please remove or reassign all labels which using this tag in your project.",
|
notCompatibleTagType: "Tag type is not compatible with this feature. If you want to change type of this tag, please remove or reassign all labels which using this tag in your project.",
|
||||||
checkboxPerTagLimit: "Cannot assign more than one checkbox per tag",
|
checkboxPerTagLimit: "Cannot assign more than one checkbox per tag",
|
||||||
notCompatibleWithDrawnRegionTag: "drawnRegion and ${otherCategory} values cannot both be assigned to the same document's tag",
|
notCompatibleWithDrawnRegionTag: "Drawn regions and ${otherCatagory} values cannot both be assigned to the same document's tag",
|
||||||
},
|
},
|
||||||
regionTableTags: {
|
regionTableTags: {
|
||||||
configureTag: {
|
configureTag: {
|
||||||
|
@ -476,10 +480,14 @@ export const english: IAppStrings = {
|
||||||
subIMenuItems: {
|
subIMenuItems: {
|
||||||
runOcrOnCurrentDocument: "Run OCR on current document",
|
runOcrOnCurrentDocument: "Run OCR on current document",
|
||||||
runOcrOnAllDocuments: "Run OCR on all documents",
|
runOcrOnAllDocuments: "Run OCR on all documents",
|
||||||
runAutoLabelingCurrentDocument: "Run AutoLabeling on current document",
|
runAutoLabelingCurrentDocument: "Auto-label the current document",
|
||||||
|
runAutoLabelingOnNotLabelingDocuments: "Auto-label next ${batchSize} unlabeled documents",
|
||||||
noPredictModelOnProject: "Predict model not avaliable, please train the model first.",
|
noPredictModelOnProject: "Predict model not avaliable, please train the model first.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
warings: {
|
||||||
|
drawRegionUnsupportedAPIVersion: "Region labeling is not supported with API ${apiVersion}. It will be supported with the release of v2.1-preview.3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -604,6 +612,10 @@ export const english: IAppStrings = {
|
||||||
message: `An error occured while deleting the project.
|
message: `An error occured while deleting the project.
|
||||||
Validate the project file and security token exist and try again`,
|
Validate the project file and security token exist and try again`,
|
||||||
},
|
},
|
||||||
|
projectDeleteErrorSecurityTokenNotFound: {
|
||||||
|
title: "Security token not found when delete project",
|
||||||
|
message: "Security Token Not Found. Project [${project.name}] has been removed from FoTT tool."
|
||||||
|
},
|
||||||
projectNotFound: {
|
projectNotFound: {
|
||||||
title: "Error loading project",
|
title: "Error loading project",
|
||||||
message: "We couldn't find the project file ${file} at the target blob container ${container}.\
|
message: "We couldn't find the project file ${file} at the target blob container ${container}.\
|
||||||
|
@ -701,6 +713,10 @@ export const english: IAppStrings = {
|
||||||
title: "Model not found",
|
title: "Model not found",
|
||||||
message: "Model \"${modelID}\" not found. Please use another model.",
|
message: "Model \"${modelID}\" not found. Please use another model.",
|
||||||
},
|
},
|
||||||
|
connectionNotExistError: {
|
||||||
|
title: "Connection doesn't exist",
|
||||||
|
message: "Connection doesn't exist."
|
||||||
|
},
|
||||||
getOcrError: {
|
getOcrError: {
|
||||||
title: "Cannot load OCR file",
|
title: "Cannot load OCR file",
|
||||||
message: "Failed to load from OCR file. Please check your connection or network settings.",
|
message: "Failed to load from OCR file. Please check your connection or network settings.",
|
||||||
|
|
|
@ -130,6 +130,10 @@ export const spanish: IAppStrings = {
|
||||||
backEndNotAvailable: "La función de casilla de verificación funcionará en la versión futura del servicio de reconocimiento de formularios, manténgase atento.",
|
backEndNotAvailable: "La función de casilla de verificación funcionará en la versión futura del servicio de reconocimiento de formularios, manténgase atento.",
|
||||||
addName: "Agregar nombre de modelo ...",
|
addName: "Agregar nombre de modelo ...",
|
||||||
downloadJson: "Descargar archivo JSON",
|
downloadJson: "Descargar archivo JSON",
|
||||||
|
trainConfirm: {
|
||||||
|
title: "Etiquetas no revisadas todavía",
|
||||||
|
message: "Tiene archivos de etiquetas que aún no han sido revisados, ¿desea entrenar con esos archivos?"
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
electron: {
|
electron: {
|
||||||
cantAccessFiles: "No se puede acceder a los archivos en '${folderUri}' para entrenamiento. Compruebe si el URI de la carpeta especificada es correcto."
|
cantAccessFiles: "No se puede acceder a los archivos en '${folderUri}' para entrenamiento. Compruebe si el URI de la carpeta especificada es correcto."
|
||||||
|
@ -477,10 +481,14 @@ export const spanish: IAppStrings = {
|
||||||
subIMenuItems: {
|
subIMenuItems: {
|
||||||
runOcrOnCurrentDocument: "Ejecutar OCR en el documento actual",
|
runOcrOnCurrentDocument: "Ejecutar OCR en el documento actual",
|
||||||
runOcrOnAllDocuments: "Ejecute OCR en todos los documentos",
|
runOcrOnAllDocuments: "Ejecute OCR en todos los documentos",
|
||||||
runAutoLabelingCurrentDocument: "Ejecutar AutoLabeling en el documento actual",
|
runAutoLabelingCurrentDocument: "Etiquetar automáticamente el documento actual",
|
||||||
|
runAutoLabelingOnNotLabelingDocuments: "Etiquetar automáticamente los siguientes ${batchSize} documentos sin etiquetar",
|
||||||
noPredictModelOnProject: "Predecir modelo no disponible, entrene el modelo primero.",
|
noPredictModelOnProject: "Predecir modelo no disponible, entrene el modelo primero.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
warings: {
|
||||||
|
drawRegionUnsupportedAPIVersion: "Las regiones de dibujo no son compatibles con la versión de API ${apiVersion}. Será compatible con el lanzamiento de v2.1-preview.3",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -604,6 +612,10 @@ export const spanish: IAppStrings = {
|
||||||
message: `Se ha producido un error al eliminar el proyecto.
|
message: `Se ha producido un error al eliminar el proyecto.
|
||||||
Validar el archivo de proyecto y el token de seguridad existen e inténtelo de nuevo`,
|
Validar el archivo de proyecto y el token de seguridad existen e inténtelo de nuevo`,
|
||||||
},
|
},
|
||||||
|
projectDeleteErrorSecurityTokenNotFound: {
|
||||||
|
title: 'No se encontró el token de seguridad al eliminar el proyecto',
|
||||||
|
message: "Token de seguridad no encontrado. El proyecto [$ {project.name}] se ha eliminado de la herramienta FoTT."
|
||||||
|
},
|
||||||
projectNotFound: {
|
projectNotFound: {
|
||||||
title: "",
|
title: "",
|
||||||
message: "",
|
message: "",
|
||||||
|
@ -701,6 +713,10 @@ export const spanish: IAppStrings = {
|
||||||
title: "Modelo no encontrado",
|
title: "Modelo no encontrado",
|
||||||
message: "Modelo \"${modelID}\" no encontrado. Por favor use otro modelo.",
|
message: "Modelo \"${modelID}\" no encontrado. Por favor use otro modelo.",
|
||||||
},
|
},
|
||||||
|
connectionNotExistError: {
|
||||||
|
title: "La conexión no existe",
|
||||||
|
message: "La conexión no existe."
|
||||||
|
},
|
||||||
getOcrError: {
|
getOcrError: {
|
||||||
title: "No se puede cargar el archivo OCR",
|
title: "No se puede cargar el archivo OCR",
|
||||||
message: "Error al cargar desde el archivo OCR. Verifique su conexión o configuración de red."
|
message: "Error al cargar desde el archivo OCR. Verifique su conexión o configuración de red."
|
||||||
|
|
|
@ -319,6 +319,7 @@ export default class MockFactory {
|
||||||
createContainer: jest.fn(),
|
createContainer: jest.fn(),
|
||||||
deleteContainer: jest.fn(),
|
deleteContainer: jest.fn(),
|
||||||
getAssets: jest.fn(),
|
getAssets: jest.fn(),
|
||||||
|
isFileExists: jest.fn(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,10 @@ export interface IAppStrings {
|
||||||
backEndNotAvailable: string,
|
backEndNotAvailable: string,
|
||||||
addName: string,
|
addName: string,
|
||||||
downloadJson: string;
|
downloadJson: string;
|
||||||
|
trainConfirm: {
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
electron: {
|
electron: {
|
||||||
cantAccessFiles: string;
|
cantAccessFiles: string;
|
||||||
|
@ -472,9 +476,13 @@ export interface IAppStrings {
|
||||||
runOcrOnCurrentDocument: string,
|
runOcrOnCurrentDocument: string,
|
||||||
runOcrOnAllDocuments: string,
|
runOcrOnAllDocuments: string,
|
||||||
runAutoLabelingCurrentDocument: string,
|
runAutoLabelingCurrentDocument: string,
|
||||||
|
runAutoLabelingOnNotLabelingDocuments: string,
|
||||||
noPredictModelOnProject: string,
|
noPredictModelOnProject: string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
warings: {
|
||||||
|
drawRegionUnsupportedAPIVersion: string,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -576,6 +584,7 @@ export interface IAppStrings {
|
||||||
projectInvalidSecurityToken: IErrorMetadata,
|
projectInvalidSecurityToken: IErrorMetadata,
|
||||||
projectUploadError: IErrorMetadata,
|
projectUploadError: IErrorMetadata,
|
||||||
projectDeleteError: IErrorMetadata,
|
projectDeleteError: IErrorMetadata,
|
||||||
|
projectDeleteErrorSecurityTokenNotFound: IErrorMetadata,
|
||||||
projectNotFound: IErrorMetadata,
|
projectNotFound: IErrorMetadata,
|
||||||
genericRenderError: IErrorMetadata,
|
genericRenderError: IErrorMetadata,
|
||||||
securityTokenNotFound: IErrorMetadata,
|
securityTokenNotFound: IErrorMetadata,
|
||||||
|
@ -600,6 +609,7 @@ export interface IAppStrings {
|
||||||
modelCountLimitExceeded: IErrorMetadata,
|
modelCountLimitExceeded: IErrorMetadata,
|
||||||
requestSendError: IErrorMetadata,
|
requestSendError: IErrorMetadata,
|
||||||
modelNotFound: IErrorMetadata,
|
modelNotFound: IErrorMetadata,
|
||||||
|
connectionNotExistError: IErrorMetadata,
|
||||||
getOcrError: IErrorMetadata,
|
getOcrError: IErrorMetadata,
|
||||||
};
|
};
|
||||||
shareProject: {
|
shareProject: {
|
||||||
|
|
|
@ -7,217 +7,37 @@
|
||||||
"hashFontFileName": true,
|
"hashFontFileName": true,
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
{
|
{
|
||||||
"name": "Table",
|
"name": "Add",
|
||||||
"unicode": "ED86"
|
"unicode": "E710"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "TextField",
|
|
||||||
"unicode": "EDC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "OpenFolderHorizontal",
|
|
||||||
"unicode": "ED25"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Documentation",
|
|
||||||
"unicode": "EC17"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "AddTo",
|
|
||||||
"unicode": "ECC8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SortUp",
|
|
||||||
"unicode": "EE68"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SortDown",
|
|
||||||
"unicode": "EE69"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Info",
|
|
||||||
"unicode": "E946"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ChromeMinimize",
|
|
||||||
"unicode": "E921"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ChromeRestore",
|
|
||||||
"unicode": "E923"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Label",
|
|
||||||
"unicode": "E932"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Copy",
|
|
||||||
"unicode": "E8C8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Rename",
|
|
||||||
"unicode": "E8AC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Download",
|
|
||||||
"unicode": "E896"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Help",
|
|
||||||
"unicode": "E897"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ZoomIn",
|
|
||||||
"unicode": "E8A3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Tag",
|
|
||||||
"unicode": "E8EC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CircleRing",
|
|
||||||
"unicode": "EA3A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SquareShape",
|
|
||||||
"unicode": "F1A6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "RectangleShape",
|
|
||||||
"unicode": "F1A9"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DocumentManagement",
|
|
||||||
"unicode": "EFFC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Relationship",
|
|
||||||
"unicode": "F003"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "TextDocument",
|
|
||||||
"unicode": "F029"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "StatusCircleCheckmark",
|
|
||||||
"unicode": "F13E"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "PlugConnected",
|
|
||||||
"unicode": "F302"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Plug",
|
|
||||||
"unicode": "F300"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "AlertSolid",
|
|
||||||
"unicode": "F331"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BranchMerge",
|
|
||||||
"unicode": "F295"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "View",
|
|
||||||
"unicode": "E890"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ReceiptProcessing",
|
|
||||||
"unicode": "E496"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AddField",
|
"name": "AddField",
|
||||||
"unicode": "E4C7"
|
"unicode": "E4C7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "TagGroup",
|
"name": "AddTo",
|
||||||
"unicode": "E3F6"
|
"unicode": "ECC8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Insights",
|
"name": "AlertSolid",
|
||||||
"unicode": "E3AF"
|
"unicode": "F331"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "MachineLearning",
|
"name": "AzureAPIManagement",
|
||||||
"unicode": "E3B8"
|
"unicode": "F37F"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Merge",
|
"name": "BookAnswers",
|
||||||
"unicode": "E7D5"
|
"unicode": "F8A4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "MapLayers",
|
"name": "BranchMerge",
|
||||||
"unicode": "E81E"
|
"unicode": "F295"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Home",
|
|
||||||
"unicode": "E80F"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ZoomOut",
|
|
||||||
"unicode": "E71F"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Search",
|
|
||||||
"unicode": "E721"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Refresh",
|
|
||||||
"unicode": "E72C"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Share",
|
|
||||||
"unicode": "E72D"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Link",
|
|
||||||
"unicode": "E71B"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ChevronDown",
|
|
||||||
"unicode": "E70D"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ChevronUp",
|
|
||||||
"unicode": "E70E"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Edit",
|
|
||||||
"unicode": "E70F"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Add",
|
|
||||||
"unicode": "E710"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Cancel",
|
"name": "Cancel",
|
||||||
"unicode": "E711"
|
"unicode": "E711"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "More",
|
|
||||||
"unicode": "E712"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Settings",
|
|
||||||
"unicode": "E713"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Filter",
|
|
||||||
"unicode": "E71C"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ChevronLeft",
|
|
||||||
"unicode": "E76B"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ChevronRight",
|
|
||||||
"unicode": "E76C"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "System",
|
|
||||||
"unicode": "E770"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "CheckboxComposite",
|
"name": "CheckboxComposite",
|
||||||
"unicode": "E73A"
|
"unicode": "E73A"
|
||||||
|
@ -227,36 +47,228 @@
|
||||||
"unicode": "E73E"
|
"unicode": "E73E"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Down",
|
"name": "ChevronDown",
|
||||||
"unicode": "E74B"
|
"unicode": "E70D"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Delete",
|
"name": "ChevronLeft",
|
||||||
"unicode": "E74D"
|
"unicode": "E76B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ChevronRight",
|
||||||
|
"unicode": "E76C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ChevronUp",
|
||||||
|
"unicode": "E70E"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ChromeMinimize",
|
||||||
|
"unicode": "E921"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ChromeRestore",
|
||||||
|
"unicode": "E923"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CircleRing",
|
||||||
|
"unicode": "EA3A"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Cloud",
|
"name": "Cloud",
|
||||||
"unicode": "E753"
|
"unicode": "E753"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Up",
|
"name": "Copy",
|
||||||
"unicode": "E74A"
|
"unicode": "E8C8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "KeyPhraseExtraction",
|
"name": "Delete",
|
||||||
"unicode": "E395"
|
"unicode": "E74D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Documentation",
|
||||||
|
"unicode": "EC17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DocumentManagement",
|
||||||
|
"unicode": "EFFC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Down",
|
||||||
|
"unicode": "E74B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Download",
|
||||||
|
"unicode": "E896"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Edit",
|
||||||
|
"unicode": "E70F"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Filter",
|
||||||
|
"unicode": "E71C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Help",
|
||||||
|
"unicode": "E897"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Hide3",
|
"name": "Hide3",
|
||||||
"unicode": "F6AC"
|
"unicode": "F6AC"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Home",
|
||||||
|
"unicode": "E80F"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Info",
|
||||||
|
"unicode": "E946"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Insights",
|
||||||
|
"unicode": "E3AF"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "KeyPhraseExtraction",
|
||||||
|
"unicode": "E395"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Label",
|
||||||
|
"unicode": "E932"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Link",
|
||||||
|
"unicode": "E71B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MachineLearning",
|
||||||
|
"unicode": "E3B8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MapLayers",
|
||||||
|
"unicode": "E81E"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Merge",
|
||||||
|
"unicode": "E7D5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "More",
|
||||||
|
"unicode": "E712"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "OpenFolderHorizontal",
|
||||||
|
"unicode": "ED25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Plug",
|
||||||
|
"unicode": "F300"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PlugConnected",
|
||||||
|
"unicode": "F302"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ReceiptProcessing",
|
||||||
|
"unicode": "E496"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RectangleShape",
|
||||||
|
"unicode": "F1A9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Refresh",
|
||||||
|
"unicode": "E72C"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Relationship",
|
||||||
|
"unicode": "F003"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rename",
|
||||||
|
"unicode": "E8AC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rotate90Clockwise",
|
||||||
|
"unicode": "F80D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rotate90CounterClockwise",
|
||||||
|
"unicode": "F80E"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Search",
|
||||||
|
"unicode": "E721"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Settings",
|
||||||
|
"unicode": "E713"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Share",
|
||||||
|
"unicode": "E72D"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SortDown",
|
||||||
|
"unicode": "EE69"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SortUp",
|
||||||
|
"unicode": "EE68"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SquareShape",
|
||||||
|
"unicode": "F1A6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "StatusCircleCheckmark",
|
||||||
|
"unicode": "F13E"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "System",
|
||||||
|
"unicode": "E770"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Table",
|
||||||
|
"unicode": "ED86"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tag",
|
||||||
|
"unicode": "E8EC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TagGroup",
|
||||||
|
"unicode": "E3F6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TextDocument",
|
||||||
|
"unicode": "F029"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TextField",
|
||||||
|
"unicode": "EDC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Up",
|
||||||
|
"unicode": "E74A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "View",
|
||||||
|
"unicode": "E890"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "WarningSolid",
|
"name": "WarningSolid",
|
||||||
"unicode": "F736"
|
"unicode": "F736"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "BookAnswers",
|
"name": "ZoomIn",
|
||||||
"unicode": "F8A4"
|
"unicode": "E8A3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ZoomOut",
|
||||||
|
"unicode": "E71F"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -123,6 +123,10 @@ export default class LocalFileSystem implements IStorageProvider {
|
||||||
return this.listItems(path.normalize(folderPath), (stats) => !stats.isDirectory());
|
return this.listItems(path.normalize(folderPath), (stats) => !stats.isDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isFileExists(filePath: string): Promise<boolean> {
|
||||||
|
return Promise.resolve(fs.existsSync(path.normalize(filePath)));
|
||||||
|
}
|
||||||
|
|
||||||
public listContainers(folderPath: string): Promise<string[]> {
|
public listContainers(folderPath: string): Promise<string[]> {
|
||||||
return this.listItems(path.normalize(folderPath), (stats) => stats.isDirectory());
|
return this.listItems(path.normalize(folderPath), (stats) => stats.isDirectory());
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,7 @@ export interface IProject {
|
||||||
lastVisitedAssetId?: string,
|
lastVisitedAssetId?: string,
|
||||||
apiUriBase: string,
|
apiUriBase: string,
|
||||||
apiKey?: string | ISecureString,
|
apiKey?: string | ISecureString,
|
||||||
|
apiVersion?: string;
|
||||||
folderPath: string,
|
folderPath: string,
|
||||||
trainRecord: ITrainRecordProps,
|
trainRecord: ITrainRecordProps,
|
||||||
recentModelRecords: IRecentModel[],
|
recentModelRecords: IRecentModel[],
|
||||||
|
@ -166,6 +167,7 @@ export interface IAsset {
|
||||||
id: string,
|
id: string,
|
||||||
type: AssetType,
|
type: AssetType,
|
||||||
state: AssetState,
|
state: AssetState,
|
||||||
|
labelingState?: AssetLabelingState,
|
||||||
name: string,
|
name: string,
|
||||||
path: string,
|
path: string,
|
||||||
size: ISize,
|
size: ISize,
|
||||||
|
@ -174,7 +176,9 @@ export interface IAsset {
|
||||||
predicted?: boolean,
|
predicted?: boolean,
|
||||||
ocr?: any,
|
ocr?: any,
|
||||||
isRunningOCR?: boolean,
|
isRunningOCR?: boolean,
|
||||||
|
isRunningAutoLabeling?: boolean,
|
||||||
cachedImage?: string,
|
cachedImage?: string,
|
||||||
|
mimeType?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,6 +223,8 @@ export interface IRegion {
|
||||||
value?: string,
|
value?: string,
|
||||||
pageNumber: number,
|
pageNumber: number,
|
||||||
isTableRegion?: boolean,
|
isTableRegion?: boolean,
|
||||||
|
changed?: boolean,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITableRegion extends IRegion {
|
export interface ITableRegion extends IRegion {
|
||||||
|
@ -232,6 +238,7 @@ export interface ITableRegion extends IRegion {
|
||||||
*/
|
*/
|
||||||
export interface ILabelData {
|
export interface ILabelData {
|
||||||
document: string,
|
document: string,
|
||||||
|
labelingState?: AssetLabelingState;
|
||||||
labels: ILabel[],
|
labels: ILabel[],
|
||||||
tableLabels?: ITableLabel[],
|
tableLabels?: ITableLabel[],
|
||||||
}
|
}
|
||||||
|
@ -245,6 +252,7 @@ export interface ILabel {
|
||||||
key?: IFormRegion[],
|
key?: IFormRegion[],
|
||||||
value: IFormRegion[],
|
value: IFormRegion[],
|
||||||
labelType?: string,
|
labelType?: string,
|
||||||
|
confidence?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITableLabel {
|
export interface ITableLabel {
|
||||||
|
@ -356,6 +364,12 @@ export enum ErrorCode {
|
||||||
ProjectUploadError = "ProjectUploadError",
|
ProjectUploadError = "ProjectUploadError",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum APIVersionPatches {
|
||||||
|
patch1 = "v2.1-preview.1",
|
||||||
|
patch2 = "v2.1-preview.2",
|
||||||
|
patch3 = "v2.1-preview.3",
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum LOCAL - Local storage type
|
* @enum LOCAL - Local storage type
|
||||||
* @enum CLOUD - Cloud storage type
|
* @enum CLOUD - Cloud storage type
|
||||||
|
@ -381,6 +395,14 @@ export enum AssetType {
|
||||||
TIFF = 6,
|
TIFF = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AssetMimeType {
|
||||||
|
PDF = "application/pdf",
|
||||||
|
TIFF = "image/tiff",
|
||||||
|
JPG = "image/jpg",
|
||||||
|
PNG = "image/png",
|
||||||
|
BMP = "image/bmp",
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name - Asset State
|
* @name - Asset State
|
||||||
* @description - Defines the state of the asset with regard to the tagging process
|
* @description - Defines the state of the asset with regard to the tagging process
|
||||||
|
@ -393,6 +415,20 @@ export enum AssetState {
|
||||||
Visited = 1,
|
Visited = 1,
|
||||||
Tagged = 2,
|
Tagged = 2,
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @name - Asset Labeling State
|
||||||
|
* @description - Defines the labeling state for the asset
|
||||||
|
* @member ManualLabeling - Specifies as an asset that has manual labeling the tags
|
||||||
|
* @member Training - Specifies as an asset tagged data has been used for training model
|
||||||
|
* @member AutoLabeling - Specifies as an asset that has run auto-labeling
|
||||||
|
* @member AutoLabeledAndAdjusted -specifies as an asset that has run auto-labeling and tags manual adjusted
|
||||||
|
*/
|
||||||
|
export enum AssetLabelingState {
|
||||||
|
ManuallyLabeled = 1,
|
||||||
|
Trained = 2,
|
||||||
|
AutoLabeled = 3,
|
||||||
|
AutoLabeledAndAdjusted = 4,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name - Region Type
|
* @name - Region Type
|
||||||
|
@ -430,7 +466,7 @@ export enum FieldType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LabelType {
|
export enum LabelType {
|
||||||
DrawnRegion = "drawnRegion"
|
DrawnRegion = "region"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FieldFormat {
|
export enum FieldFormat {
|
||||||
|
@ -451,7 +487,7 @@ export enum FeatureCategory {
|
||||||
Text = "text",
|
Text = "text",
|
||||||
Checkbox = "checkbox",
|
Checkbox = "checkbox",
|
||||||
Label = "label",
|
Label = "label",
|
||||||
DrawnRegion = "drawnRegion"
|
DrawnRegion = "region"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImageMapParent {
|
export enum ImageMapParent {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { BlobServiceClient, ContainerClient } from "@azure/storage-blob";
|
import { BlobServiceClient, ContainerClient } from "@azure/storage-blob";
|
||||||
import { constants } from "../../common/constants";
|
import { constants } from "../../common/constants";
|
||||||
import { strings } from "../../common/strings";
|
import { strings } from "../../common/strings";
|
||||||
import { AppError, AssetState, AssetType, ErrorCode, IAsset, StorageType } from "../../models/applicationState";
|
import { AppError, AssetState, AssetType, ErrorCode, IAsset, StorageType, ILabelData, AssetLabelingState } from "../../models/applicationState";
|
||||||
import { throwUnhandledRejectionForEdge } from "../../react/components/common/errorHandler/errorHandler";
|
import { throwUnhandledRejectionForEdge } from "../../react/components/common/errorHandler/errorHandler";
|
||||||
import { AssetService } from "../../services/assetService";
|
import { AssetService } from "../../services/assetService";
|
||||||
import { IStorageProvider } from "./storageProviderFactory";
|
import { IStorageProvider } from "./storageProviderFactory";
|
||||||
|
@ -150,6 +150,14 @@ export class AzureBlobStorage implements IStorageProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check file is exists
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
public async isFileExists(filePath: string) :Promise<boolean> {
|
||||||
|
const client = this.containerClient.getBlobClient(filePath);
|
||||||
|
return await client.exists();
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Lists the containers with in the Azure Blob Storage account
|
* Lists the containers with in the Azure Blob Storage account
|
||||||
* @param path - NOT USED IN CURRENT IMPLEMENTATION. Lists containers in storage account.
|
* @param path - NOT USED IN CURRENT IMPLEMENTATION. Lists containers in storage account.
|
||||||
|
@ -206,12 +214,17 @@ export class AzureBlobStorage implements IStorageProvider {
|
||||||
|
|
||||||
if (files.find((str) => str === labelFileName)) {
|
if (files.find((str) => str === labelFileName)) {
|
||||||
asset.state = AssetState.Tagged;
|
asset.state = AssetState.Tagged;
|
||||||
|
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
|
||||||
|
const json = await this.readText(labelFileName, true);
|
||||||
|
const labelData = JSON.parse(json) as ILabelData;
|
||||||
|
if (labelData) {
|
||||||
|
asset.labelingState = labelData.labelingState || AssetLabelingState.ManuallyLabeled;
|
||||||
|
}
|
||||||
} else if (files.find((str) => str === ocrFileName)) {
|
} else if (files.find((str) => str === ocrFileName)) {
|
||||||
asset.state = AssetState.Visited;
|
asset.state = AssetState.Visited;
|
||||||
} else {
|
} else {
|
||||||
asset.state = AssetState.NotVisited;
|
asset.state = AssetState.NotVisited;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(asset);
|
result.push(asset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,15 @@ export class LocalFileSystemProxy implements IStorageProvider, IAssetProvider {
|
||||||
return IpcRendererProxy.send(`${PROXY_NAME}:listFiles`, [folderPath]);
|
return IpcRendererProxy.send(`${PROXY_NAME}:listFiles`, [folderPath]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check file is exists
|
||||||
|
* @param fileName Name of target file
|
||||||
|
*/
|
||||||
|
public isFileExists(fileName: string): Promise<boolean> {
|
||||||
|
const filePath = [this.options.folderPath, fileName].join("/");
|
||||||
|
return IpcRendererProxy.send(`${PROXY_NAME}:isFileExists`, [filePath]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List directories inside another directory
|
* List directories inside another directory
|
||||||
* @param folderName - Directory from which to list directories
|
* @param folderName - Directory from which to list directories
|
||||||
|
|
|
@ -48,6 +48,9 @@ class TestStorageProvider implements IStorageProvider {
|
||||||
public listFiles(folderPath?: string): Promise<string[]> {
|
public listFiles(folderPath?: string): Promise<string[]> {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
|
isFileExists(filepath: string): Promise<boolean> {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
public listContainers(folderPath?: string): Promise<string[]> {
|
public listContainers(folderPath?: string): Promise<string[]> {
|
||||||
throw new Error("Method not implemented.");
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ export interface IStorageProvider extends IAssetProvider {
|
||||||
isValidProjectConnection(filepath?): Promise<boolean>;
|
isValidProjectConnection(filepath?): Promise<boolean>;
|
||||||
|
|
||||||
listFiles(folderPath?: string, ext?: string): Promise<string[]>;
|
listFiles(folderPath?: string, ext?: string): Promise<string[]>;
|
||||||
|
isFileExists(filepath: string): Promise<boolean>;
|
||||||
listContainers(folderPath?: string): Promise<string[]>;
|
listContainers(folderPath?: string): Promise<string[]>;
|
||||||
|
|
||||||
createContainer(folderPath: string): Promise<void>;
|
createContainer(folderPath: string): Promise<void>;
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
import React, { SyntheticEvent } from "react";
|
||||||
|
import { APIVersionPatches } from "../../../../models/applicationState";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* api version Picker Properties
|
||||||
|
* @member id - The id to bind to the input element
|
||||||
|
* @member value - The value to bind to the input element
|
||||||
|
* @member onChange - The event handler to call when the input value changes
|
||||||
|
*/
|
||||||
|
export interface IAPIVersionPickerProps {
|
||||||
|
id?: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* api version Picker
|
||||||
|
*/
|
||||||
|
export class APIVersionPicker extends React.Component<IAPIVersionPickerProps> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onChange = this.onChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<select id={this.props.id}
|
||||||
|
className="form-control"
|
||||||
|
value={this.props.value}
|
||||||
|
onChange={this.onChange}>
|
||||||
|
<option value={APIVersionPatches.patch1}>{APIVersionPatches.patch1}</option>
|
||||||
|
<option value={APIVersionPatches.patch2}>{APIVersionPatches.patch2}</option>
|
||||||
|
<option value={APIVersionPatches.patch3}>{APIVersionPatches.patch3 + " (testing)"}</option>
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onChange(e: SyntheticEvent) {
|
||||||
|
const inputElement = e.target as HTMLSelectElement;
|
||||||
|
this.props.onChange(inputElement.value ? inputElement.value : "2.1-preview.3");
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,6 +114,14 @@ export class AssetPreview extends React.Component<IAssetPreviewProps, IAssetPrev
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{this.props.asset.isRunningAutoLabeling &&
|
||||||
|
<div className="asset-loading">
|
||||||
|
<div className="asset-loading-ocr-spinner">
|
||||||
|
<Label className="p-0" ></Label>
|
||||||
|
<Spinner size={SpinnerSize.small} label="Auto Labeling..." ariaLive="off" labelPosition="right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{hasError &&
|
{hasError &&
|
||||||
<div className="asset-error text-danger">
|
<div className="asset-error text-danger">
|
||||||
<i className="fas fa-2x fa-exclamation-circle" />
|
<i className="fas fa-2x fa-exclamation-circle" />
|
||||||
|
|
|
@ -42,6 +42,9 @@ ul.condensed-list-items {
|
||||||
&.active, &:hover {
|
&.active, &:hover {
|
||||||
background-color: $lighter-2;
|
background-color: $lighter-2;
|
||||||
}
|
}
|
||||||
|
&.current{
|
||||||
|
background-color: $lighter-3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,18 @@ interface ICondensedListProps {
|
||||||
onDelete?: (item) => void;
|
onDelete?: (item) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ICondensedListState {
|
||||||
|
currentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name - Condensed List
|
* @name - Condensed List
|
||||||
* @description - Clickable, deletable and linkable list of items
|
* @description - Clickable, deletable and linkable list of items
|
||||||
*/
|
*/
|
||||||
export default class CondensedList extends React.Component<ICondensedListProps> {
|
export default class CondensedList extends React.Component<ICondensedListProps, ICondensedListState> {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
this.state = { currentId: null };
|
||||||
|
|
||||||
this.onItemClick = this.onItemClick.bind(this);
|
this.onItemClick = this.onItemClick.bind(this);
|
||||||
this.onItemDelete = this.onItemDelete.bind(this);
|
this.onItemDelete = this.onItemDelete.bind(this);
|
||||||
|
@ -66,6 +71,7 @@ export default class CondensedList extends React.Component<ICondensedListProps>
|
||||||
<ul className="condensed-list-items">
|
<ul className="condensed-list-items">
|
||||||
{items.map((item) => <Component key={item.id}
|
{items.map((item) => <Component key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
|
currentId={this.state.currentId}
|
||||||
onClick={(e) => this.onItemClick(e, item)}
|
onClick={(e) => this.onItemClick(e, item)}
|
||||||
onDelete={(e) => this.onItemDelete(e, item)} />)}
|
onDelete={(e) => this.onItemDelete(e, item)} />)}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -79,6 +85,7 @@ export default class CondensedList extends React.Component<ICondensedListProps>
|
||||||
if (this.props.onClick) {
|
if (this.props.onClick) {
|
||||||
this.props.onClick(item);
|
this.props.onClick(item);
|
||||||
}
|
}
|
||||||
|
this.setState({ currentId: item.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onItemDelete = (e: SyntheticEvent, item) => {
|
private onItemDelete = (e: SyntheticEvent, item) => {
|
||||||
|
@ -95,11 +102,11 @@ export default class CondensedList extends React.Component<ICondensedListProps>
|
||||||
* Generic list item with an onClick function and a name
|
* Generic list item with an onClick function and a name
|
||||||
* @param param0 - {item: {name: ""}, onClick: (item) => void;}
|
* @param param0 - {item: {name: ""}, onClick: (item) => void;}
|
||||||
*/
|
*/
|
||||||
export function ListItem({ item, onClick }) {
|
export function ListItem({ item, onClick, currentId }) {
|
||||||
return (
|
return (
|
||||||
<li>
|
<li>
|
||||||
{/* eslint-disable-next-line */}
|
{/* eslint-disable-next-line */}
|
||||||
<a className="condensed-list-item" onClick={onClick}>
|
<a className={["condensed-list-item", currentId === item.id? "current":""].join(" ")} onClick={onClick}>
|
||||||
<span className="px-2">{item.name}</span>
|
<span className="px-2">{item.name}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -43,6 +43,7 @@ interface IImageMapProps {
|
||||||
|
|
||||||
enableFeatureSelection?: boolean;
|
enableFeatureSelection?: boolean;
|
||||||
handleFeatureSelect?: (feature: any, isTaggle: boolean, category: FeatureCategory) => void;
|
handleFeatureSelect?: (feature: any, isTaggle: boolean, category: FeatureCategory) => void;
|
||||||
|
handleFeatureDoubleClick?: (feature: any, isTaggle: boolean, category: FeatureCategory) => void;
|
||||||
groupSelectMode?: boolean;
|
groupSelectMode?: boolean;
|
||||||
handleIsPointerOnImage?: (isPointerOnImage: boolean) => void;
|
handleIsPointerOnImage?: (isPointerOnImage: boolean) => void;
|
||||||
isPointerOnImage?: boolean;
|
isPointerOnImage?: boolean;
|
||||||
|
@ -361,7 +362,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
||||||
*/
|
*/
|
||||||
public addInteraction = (interaction: Interaction) => {
|
public addInteraction = (interaction: Interaction) => {
|
||||||
if (undefined === this.map.getInteractions().array_.find((existingInteraction) => {
|
if (undefined === this.map.getInteractions().array_.find((existingInteraction) => {
|
||||||
return interaction.constructor.name === existingInteraction.constructor.name
|
return interaction.constructor === existingInteraction.constructor;
|
||||||
})) {
|
})) {
|
||||||
this.map.addInteraction(interaction);
|
this.map.addInteraction(interaction);
|
||||||
}
|
}
|
||||||
|
@ -516,7 +517,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
||||||
*/
|
*/
|
||||||
public removeInteraction = (interaction: Interaction) => {
|
public removeInteraction = (interaction: Interaction) => {
|
||||||
const existingInteraction = this.map.getInteractions().array_.find((existingInteraction) => {
|
const existingInteraction = this.map.getInteractions().array_.find((existingInteraction) => {
|
||||||
return interaction.constructor.name === existingInteraction.constructor.name
|
return interaction.constructor === existingInteraction.constructor;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingInteraction !== undefined) {
|
if (existingInteraction !== undefined) {
|
||||||
|
@ -577,6 +578,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
||||||
this.map.on("pointermove", this.handlePointerMove);
|
this.map.on("pointermove", this.handlePointerMove);
|
||||||
this.map.on("pointermove", this.handlePointerMoveOnTableIcon);
|
this.map.on("pointermove", this.handlePointerMoveOnTableIcon);
|
||||||
this.map.on("pointerup", this.handlePointerUp);
|
this.map.on("pointerup", this.handlePointerUp);
|
||||||
|
this.map.on("dblclick", this.handleDoubleClick);
|
||||||
|
|
||||||
this.initializeDefaultSelectionMode();
|
this.initializeDefaultSelectionMode();
|
||||||
this.initializeDragPan();
|
this.initializeDragPan();
|
||||||
|
@ -652,7 +654,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
||||||
const filter = this.getLayerFilterAtPixel(eventPixel);
|
const filter = this.getLayerFilterAtPixel(eventPixel);
|
||||||
|
|
||||||
const isPixelOnFeature = !!filter;
|
const isPixelOnFeature = !!filter;
|
||||||
if (isPixelOnFeature) {
|
if (isPixelOnFeature && !this.props.isSnapped) {
|
||||||
this.setDragPanInteraction(false);
|
this.setDragPanInteraction(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,6 +668,20 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private handleDoubleClick = (event: MapBrowserEvent) => {
|
||||||
|
const eventPixel = this.map.getEventPixel(event.originalEvent);
|
||||||
|
|
||||||
|
const filter = this.getLayerFilterAtPixel(eventPixel);
|
||||||
|
if (filter && this.props.handleFeatureDoubleClick) {
|
||||||
|
this.map.forEachFeatureAtPixel(
|
||||||
|
eventPixel,
|
||||||
|
(feature) => {
|
||||||
|
this.props.handleFeatureDoubleClick(feature, true, filter.category);
|
||||||
|
},
|
||||||
|
filter.layerfilter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getLayerFilterAtPixel = (eventPixel: any) => {
|
private getLayerFilterAtPixel = (eventPixel: any) => {
|
||||||
const isPointerOnLabelledFeature = this.map.hasFeatureAtPixel(
|
const isPointerOnLabelledFeature = this.map.hasFeatureAtPixel(
|
||||||
|
@ -789,6 +805,9 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setDragPanInteraction(true);
|
this.setDragPanInteraction(true);
|
||||||
|
this.removeInteraction(this.modify);
|
||||||
|
this.initializeModify();
|
||||||
|
this.addInteraction(this.modify)
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDragPanInteraction = (dragPanEnabled: boolean) => {
|
private setDragPanInteraction = (dragPanEnabled: boolean) => {
|
||||||
|
@ -855,6 +874,7 @@ export class ImageMap extends React.Component<IImageMapProps> {
|
||||||
this.initializeModify();
|
this.initializeModify();
|
||||||
this.initializeSnap();
|
this.initializeSnap();
|
||||||
this.initializeDraw();
|
this.initializeDraw();
|
||||||
|
this.addInteraction(this.dragBox);
|
||||||
this.addInteraction(this.modify);
|
this.addInteraction(this.modify);
|
||||||
this.addInteraction(this.snap);
|
this.addInteraction(this.snap);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,18 @@
|
||||||
&-container {
|
&-container {
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding: 0 0 0 100px;
|
||||||
|
margin: 0 0 0 -100px;
|
||||||
|
&::before{
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
width: 80px;
|
||||||
|
height: 100%;
|
||||||
|
left: -80px;
|
||||||
|
background: linear-gradient(to right, #00000000 0%,#000000 100%);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +85,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item-block {
|
&-item-block {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
|
@ -80,11 +93,23 @@
|
||||||
&-2 {
|
&-2 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.tag-item-confidence{
|
||||||
|
position: absolute;
|
||||||
|
line-height: 2em;
|
||||||
|
left: -70PX;
|
||||||
|
z-index: 900;
|
||||||
|
text-align: right;
|
||||||
|
width:50px;
|
||||||
|
text-shadow: 1px 1px 1px #333;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
.tag-content {
|
||||||
|
transition: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
&-selected {
|
&-selected {
|
||||||
.tag-content {
|
.tag-content {
|
||||||
|
@ -100,7 +125,12 @@
|
||||||
background: $darker-10 !important;
|
background: $darker-10 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&-highlight {
|
||||||
|
.tag-content {
|
||||||
|
background-color: $lighter-5 !important;
|
||||||
|
box-shadow:4px 4px 5px $lighter-5;
|
||||||
|
}
|
||||||
|
}
|
||||||
&-label {
|
&-label {
|
||||||
min-height: 1em;
|
min-height: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -172,7 +202,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item-label {
|
&-item-label {
|
||||||
color: #A0A0A0;
|
color: #a0a0a0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-item-label:hover {
|
&-item-label:hover {
|
||||||
|
@ -224,7 +254,7 @@
|
||||||
width: 0.1px;
|
width: 0.1px;
|
||||||
border: 0.5px solid $lighter-2;
|
border: 0.5px solid $lighter-2;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin: 0 0.25em
|
margin: 0 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-iconbutton {
|
&-iconbutton {
|
||||||
|
@ -234,7 +264,8 @@
|
||||||
padding: 0 0.25em;
|
padding: 0 0.25em;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
&.active, &:hover {
|
&.active,
|
||||||
|
&:hover {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,6 @@ export interface ITagInputProps {
|
||||||
onLabelLeave: (label: ILabel) => void;
|
onLabelLeave: (label: ILabel) => void;
|
||||||
/** Function to handle tag change */
|
/** Function to handle tag change */
|
||||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||||
|
|
||||||
setTagInputMode?: (tagInputMode: TagInputMode, selectedTableTagToLabel?: ITableTag) => void;
|
setTagInputMode?: (tagInputMode: TagInputMode, selectedTableTagToLabel?: ITableTag) => void;
|
||||||
tagInputMode: TagInputMode;
|
tagInputMode: TagInputMode;
|
||||||
selectedTableTagToLabel: ITableTag;
|
selectedTableTagToLabel: ITableTag;
|
||||||
|
@ -91,6 +90,7 @@ export interface ITagInputProps {
|
||||||
handleTableCellClick: (iTableCellIndex, jTableCellIndex) => void;
|
handleTableCellClick: (iTableCellIndex, jTableCellIndex) => void;
|
||||||
selectedTableTagBody: ITableRegion[][][];
|
selectedTableTagBody: ITableRegion[][][];
|
||||||
splitPaneWidth: number;
|
splitPaneWidth: number;
|
||||||
|
onTagDoubleClick?: (label: ILabel) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITagInputState {
|
export interface ITagInputState {
|
||||||
|
@ -207,6 +207,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
searchTags: !this.state.searchTags,
|
searchTags: !this.state.searchTags,
|
||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
})}
|
})}
|
||||||
|
searchingTags={this.state.searchQuery.length > 0}
|
||||||
onRenameTag={this.onRenameTag}
|
onRenameTag={this.onRenameTag}
|
||||||
onLockTag={this.onLockTag}
|
onLockTag={this.onLockTag}
|
||||||
onDelete={this.onDeleteTag}
|
onDelete={this.onDeleteTag}
|
||||||
|
@ -227,6 +228,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
onChange={(e) => this.setState({ searchQuery: e.target.value })}
|
onChange={(e) => this.setState({ searchQuery: e.target.value })}
|
||||||
placeholder="Search tags"
|
placeholder="Search tags"
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
onFocus={() => this.setState({ selectedTag: null, tagOperation: TagOperationMode.Rename })}
|
||||||
/>
|
/>
|
||||||
<FontIcon iconName="Search" />
|
<FontIcon iconName="Search" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -245,6 +247,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
}
|
}
|
||||||
</Customizer>
|
</Customizer>
|
||||||
{this.getColorPickerPortal()}
|
{this.getColorPickerPortal()}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
this.state.addTags &&
|
this.state.addTags &&
|
||||||
|
@ -429,6 +432,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
onLabelLeave={this.props.onLabelLeave}
|
onLabelLeave={this.props.onLabelLeave}
|
||||||
onTagChanged={this.props.onTagChanged}
|
onTagChanged={this.props.onTagChanged}
|
||||||
handleLabelTable={this.props.handleLabelTable}
|
handleLabelTable={this.props.handleLabelTable}
|
||||||
|
onTagDoubleClick={this.props.onTagDoubleClick}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,11 +523,11 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
deselect = false;
|
deselect = false;
|
||||||
} else if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
} else if (labelAssigned && ((category === FeatureCategory.DrawnRegion) !== isTagLabelTypeDrawnRegion)) {
|
||||||
if (isTagLabelTypeDrawnRegion) {
|
if (isTagLabelTypeDrawnRegion) {
|
||||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCategory: category}));
|
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: category}));
|
||||||
} else if (tagCategory === FeatureCategory.Checkbox) {
|
} else if (tagCategory === FeatureCategory.Checkbox) {
|
||||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCategory: FeatureCategory.Checkbox}));
|
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Checkbox}));
|
||||||
} else {
|
} else {
|
||||||
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, {otherCategory: FeatureCategory.Text}));
|
toast.warn(interpolate(strings.tags.warnings.notCompatibleWithDrawnRegionTag, { otherCatagory: FeatureCategory.Text}));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
|
} else if (tagCategory === category || category === FeatureCategory.DrawnRegion ||
|
||||||
|
@ -544,7 +548,16 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
focusTag(tag: string) {
|
||||||
|
const tagItemRef = this.tagItemRefs.get(tag)?.getTagNameRef();
|
||||||
|
if (tagItemRef) {
|
||||||
|
tagItemRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||||||
|
tagItemRef.current.classList.add("tag-item-highlight");
|
||||||
|
setTimeout(() => {
|
||||||
|
tagItemRef.current.classList.remove("tag-item-highlight");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
public labelAssigned = (labels: ILabel[], name): boolean => {
|
public labelAssigned = (labels: ILabel[], name): boolean => {
|
||||||
const label = labels.find((label) => label.label === name ? true : false);
|
const label = labels.find((label) => label.label === name ? true : false);
|
||||||
if (!label) {
|
if (!label) {
|
||||||
|
@ -728,6 +741,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
},
|
},
|
||||||
text: strings.tags.toolbar.moveUp,
|
text: strings.tags.toolbar.moveUp,
|
||||||
onClick: this.onMenuItemClick,
|
onClick: this.onMenuItemClick,
|
||||||
|
disabled: this.state.searchQuery.length > 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: TagMenuItem.MoveDown,
|
key: TagMenuItem.MoveDown,
|
||||||
|
@ -736,6 +750,7 @@ export class TagInput extends React.Component<ITagInputProps, ITagInputState> {
|
||||||
},
|
},
|
||||||
text: strings.tags.toolbar.moveDown,
|
text: strings.tags.toolbar.moveDown,
|
||||||
onClick: this.onMenuItemClick,
|
onClick: this.onMenuItemClick,
|
||||||
|
disabled: this.state.searchQuery.length > 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: TagMenuItem.Delete,
|
key: TagMenuItem.Delete,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { ITag, ILabel, FieldType, FieldFormat, TagInputMode } from "../../../../
|
||||||
import { strings } from "../../../../common/strings";
|
import { strings } from "../../../../common/strings";
|
||||||
import TagInputItemLabel from "./tagInputItemLabel";
|
import TagInputItemLabel from "./tagInputItemLabel";
|
||||||
import { tagIndexKeys } from "./tagIndexKeys";
|
import { tagIndexKeys } from "./tagIndexKeys";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
export interface ITagClickProps {
|
export interface ITagClickProps {
|
||||||
ctrlKey?: boolean;
|
ctrlKey?: boolean;
|
||||||
|
@ -43,6 +44,7 @@ export interface ITagInputItemProps {
|
||||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||||
handleLabelTable: (tagInputMode: TagInputMode, selectedTableTagToLabel) => void;
|
handleLabelTable: (tagInputMode: TagInputMode, selectedTableTagToLabel) => void;
|
||||||
addRowToDynamicTable: () => void;
|
addRowToDynamicTable: () => void;
|
||||||
|
onTagDoubleClick?: (label:ILabel) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITagInputItemState {
|
export interface ITagInputItemState {
|
||||||
|
@ -81,9 +83,14 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
||||||
const style: any = {
|
const style: any = {
|
||||||
background: this.props.tag.color,
|
background: this.props.tag.color,
|
||||||
};
|
};
|
||||||
|
const confidence = _.get(this.props, "labels[0].confidence", null);
|
||||||
return (
|
return (
|
||||||
<div className={"tag-item-block"}>
|
<div className={"tag-item-block"}>
|
||||||
|
{confidence &&
|
||||||
|
<div className="tag-item-confidence">
|
||||||
|
{confidence}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div
|
<div
|
||||||
className={"tag-color"}
|
className={"tag-color"}
|
||||||
style={style}
|
style={style}
|
||||||
|
@ -98,6 +105,7 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
||||||
style={style}>
|
style={style}>
|
||||||
<div
|
<div
|
||||||
className={"tag-content pr-2"}
|
className={"tag-content pr-2"}
|
||||||
|
onDoubleClick={this.onNameDoubleClick}
|
||||||
onClick={this.onNameClick}>
|
onClick={this.onNameClick}>
|
||||||
{this.getTagContent()}
|
{this.getTagContent()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -141,6 +149,14 @@ export default class TagInputItem extends React.Component<ITagInputItemProps, IT
|
||||||
this.props.onClick(this.props.tag, { ctrlKey, altKey });
|
this.props.onClick(this.props.tag, { ctrlKey, altKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onNameDoubleClick = (e:MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const { labels } = this.props;
|
||||||
|
if (labels.length > 0) {
|
||||||
|
this.props.onTagDoubleClick(labels[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getItemClassName = () => {
|
private getItemClassName = () => {
|
||||||
const classNames = ["tag-item"];
|
const classNames = ["tag-item"];
|
||||||
if (this.props.isSelected) {
|
if (this.props.isSelected) {
|
||||||
|
|
|
@ -9,7 +9,8 @@ import { ITableRegion, ITableTag, ITag, TagInputMode } from "../../../../models/
|
||||||
enum Categories {
|
enum Categories {
|
||||||
General,
|
General,
|
||||||
Separator,
|
Separator,
|
||||||
Modifier,
|
RenameModifier,
|
||||||
|
MoveModifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Properties for tag input toolbar */
|
/** Properties for tag input toolbar */
|
||||||
|
@ -30,6 +31,7 @@ export interface ITagInputToolbarProps {
|
||||||
onDelete: (tag: ITag) => void;
|
onDelete: (tag: ITag) => void;
|
||||||
/** Function to call when one of the re-order buttons is clicked */
|
/** Function to call when one of the re-order buttons is clicked */
|
||||||
onReorder: (tag: ITag, displacement: number) => void;
|
onReorder: (tag: ITag, displacement: number) => void;
|
||||||
|
searchingTags: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITagInputToolbarItemProps {
|
interface ITagInputToolbarItemProps {
|
||||||
|
@ -76,38 +78,44 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
||||||
{
|
{
|
||||||
displayName: strings.tags.toolbar.rename,
|
displayName: strings.tags.toolbar.rename,
|
||||||
icon: "Rename",
|
icon: "Rename",
|
||||||
category: Categories.Modifier,
|
category: Categories.RenameModifier,
|
||||||
handler: this.handleRename,
|
handler: this.handleRename,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: strings.tags.toolbar.moveUp,
|
displayName: strings.tags.toolbar.moveUp,
|
||||||
icon: "Up",
|
icon: "Up",
|
||||||
category: Categories.Modifier,
|
category: Categories.MoveModifier,
|
||||||
handler: this.handleMoveUp,
|
handler: this.handleMoveUp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: strings.tags.toolbar.moveDown,
|
displayName: strings.tags.toolbar.moveDown,
|
||||||
icon: "Down",
|
icon: "Down",
|
||||||
category: Categories.Modifier,
|
category: Categories.MoveModifier,
|
||||||
handler: this.handleMoveDown,
|
handler: this.handleMoveDown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: strings.tags.toolbar.delete,
|
displayName: strings.tags.toolbar.delete,
|
||||||
icon: "Delete",
|
icon: "Delete",
|
||||||
category: Categories.Modifier,
|
category: Categories.MoveModifier,
|
||||||
handler: this.handleDelete,
|
handler: this.handleDelete,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderItems = () => {
|
private renderItems = () => {
|
||||||
const modifierDisabled = !this.props.selectedTag;
|
const moveModifierDisabled = !this.props.selectedTag || this.props.searchingTags;
|
||||||
const modifierClassNames = ["tag-input-toolbar-iconbutton"];
|
const renameModifierDisabled = !this.props.selectedTag;
|
||||||
if (modifierDisabled) {
|
const moveModifierClassNames = ["tag-input-toolbar-iconbutton"];
|
||||||
modifierClassNames.push("tag-input-toolbar-iconbutton-disabled");
|
const renameModifierClassNames = ["tag-input-toolbar-iconbutton"];
|
||||||
|
if (moveModifierDisabled) {
|
||||||
|
moveModifierClassNames.push("tag-input-toolbar-iconbutton-disabled");
|
||||||
|
}
|
||||||
|
if (renameModifierDisabled) {
|
||||||
|
renameModifierClassNames.push("tag-input-toolbar-iconbutton-disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
const modifierClassName = modifierClassNames.join(" ");
|
const moveModifierClassName = moveModifierClassNames.join(" ");
|
||||||
|
const renameModifierClassName = renameModifierClassNames.join(" ");
|
||||||
|
|
||||||
return(
|
return(
|
||||||
this.getToolbarItems().map((itemConfig, index) => {
|
this.getToolbarItems().map((itemConfig, index) => {
|
||||||
|
@ -124,14 +132,25 @@ export default class TagInputToolbar extends React.Component<ITagInputToolbarPro
|
||||||
);
|
);
|
||||||
} else if (itemConfig.category === Categories.Separator) {
|
} else if (itemConfig.category === Categories.Separator) {
|
||||||
return (<div className="tag-input-toolbar-separator" key={itemConfig.displayName}></div>);
|
return (<div className="tag-input-toolbar-separator" key={itemConfig.displayName}></div>);
|
||||||
} else if (itemConfig.category === Categories.Modifier) {
|
} else if (itemConfig.category === Categories.RenameModifier) {
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
key={itemConfig.displayName}
|
key={itemConfig.displayName}
|
||||||
disabled={modifierDisabled}
|
disabled={renameModifierDisabled}
|
||||||
title={itemConfig.displayName}
|
title={itemConfig.displayName}
|
||||||
ariaLabel={itemConfig.displayName}
|
ariaLabel={itemConfig.displayName}
|
||||||
className={modifierClassName}
|
className={renameModifierClassName}
|
||||||
|
iconProps={{iconName: itemConfig.icon}}
|
||||||
|
onClick={(e) => this.onToolbarItemClick(e, itemConfig)} />
|
||||||
|
);
|
||||||
|
} else if (itemConfig.category === Categories.MoveModifier) {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
key={itemConfig.displayName}
|
||||||
|
disabled={moveModifierDisabled}
|
||||||
|
title={itemConfig.displayName}
|
||||||
|
ariaLabel={itemConfig.displayName}
|
||||||
|
className={moveModifierClassName}
|
||||||
iconProps={{iconName: itemConfig.icon}}
|
iconProps={{iconName: itemConfig.icon}}
|
||||||
onClick={(e) => this.onToolbarItemClick(e, itemConfig)} />
|
onClick={(e) => this.onToolbarItemClick(e, itemConfig)} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
background-color: $darker-1;
|
background-color: $darker-1;
|
||||||
border: solid 1px $lighter-2;
|
border: solid 1px $lighter-2;
|
||||||
color: rgb(0, 161, 241);
|
color: rgb(0, 161, 241);
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
&:hover, &.active {
|
&:hover, &.active {
|
||||||
background-color: $darker-2;
|
background-color: $darker-2;
|
||||||
|
@ -39,14 +40,14 @@
|
||||||
.prev {
|
.prev {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 0;
|
left: 50px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.next {
|
.next {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
right: 0;
|
right: 50px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +78,7 @@
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
z-index: 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas-ocr-loading-spinner {
|
.canvas-ocr-loading-spinner {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
EditorMode, IAssetMetadata,
|
EditorMode, IAssetMetadata,
|
||||||
IProject, IRegion, RegionType,
|
IProject, IRegion, RegionType,
|
||||||
AssetType, ILabelData, ILabel,
|
AssetType, ILabelData, ILabel,
|
||||||
ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, ImageMapParent, LabelType, ITableRegion, ITableTag, ITableLabel, ITableCellLabel
|
ITag, IAsset, IFormRegion, FeatureCategory, FieldType, FieldFormat, ImageMapParent, LabelType, ITableRegion, ITableTag, ITableLabel, ITableCellLabel, AssetLabelingState, APIVersionPatches
|
||||||
} from "../../../../models/applicationState";
|
} from "../../../../models/applicationState";
|
||||||
import CanvasHelpers from "./canvasHelpers";
|
import CanvasHelpers from "./canvasHelpers";
|
||||||
import { AssetPreview } from "../../common/assetPreview/assetPreview";
|
import { AssetPreview } from "../../common/assetPreview/assetPreview";
|
||||||
|
@ -37,7 +37,8 @@ import { TooltipHost, ITooltipHostStyles } from "@fluentui/react";
|
||||||
import { IAppSettings } from '../../../../models/applicationState';
|
import { IAppSettings } from '../../../../models/applicationState';
|
||||||
import { AutoLabelingStatus, PredictService } from "../../../../services/predictService";
|
import { AutoLabelingStatus, PredictService } from "../../../../services/predictService";
|
||||||
import { AssetService } from "../../../../services/assetService";
|
import { AssetService } from "../../../../services/assetService";
|
||||||
import { strings } from "../../../../common/strings";
|
import { interpolate, strings } from "../../../../common/strings";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
||||||
|
@ -55,11 +56,13 @@ export interface ICanvasProps extends React.Props<Canvas> {
|
||||||
closeTableView?: (state: string) => void;
|
closeTableView?: (state: string) => void;
|
||||||
onAssetMetadataChanged?: (assetMetadata: IAssetMetadata) => void;
|
onAssetMetadataChanged?: (assetMetadata: IAssetMetadata) => void;
|
||||||
onSelectedRegionsChanged?: (regions: IRegion[]) => void;
|
onSelectedRegionsChanged?: (regions: IRegion[]) => void;
|
||||||
|
onRegionDoubleClick?: (region: IRegion) => void;
|
||||||
onCanvasRendered?: (canvas: HTMLCanvasElement) => void;
|
onCanvasRendered?: (canvas: HTMLCanvasElement) => void;
|
||||||
onRunningOCRStatusChanged?: (isRunning: boolean) => void;
|
onRunningOCRStatusChanged?: (isRunning: boolean) => void;
|
||||||
onRunningAutoLabelingStatusChanged?: (isRunning: boolean) => void;
|
onRunningAutoLabelingStatusChanged?: (isRunning: boolean) => void;
|
||||||
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
onTagChanged?: (oldTag: ITag, newTag: ITag) => void;
|
||||||
runOcrForAllDocs?: (runForAllDocs: boolean) => void;
|
runOcrForAllDocs?: (runForAllDocs: boolean) => void;
|
||||||
|
runAutoLabelingOnNextBatch?: () => Promise<void>;
|
||||||
onAssetDeleted?: () => void;
|
onAssetDeleted?: () => void;
|
||||||
handleLabelTable?: () => void;
|
handleLabelTable?: () => void;
|
||||||
}
|
}
|
||||||
|
@ -181,7 +184,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
public componentDidUpdate = async (prevProps: Readonly<ICanvasProps>, prevState: Readonly<ICanvasState>) => {
|
public componentDidUpdate = async (prevProps: Readonly<ICanvasProps>, prevState: Readonly<ICanvasState>) => {
|
||||||
// Handles asset changing
|
// Handles asset changing
|
||||||
if (this.props.selectedAsset.asset.name !== prevProps.selectedAsset.asset.name ||
|
if (this.props.selectedAsset.asset.name !== prevProps.selectedAsset.asset.name ||
|
||||||
this.props.selectedAsset.asset.isRunningOCR !== prevProps.selectedAsset.asset.isRunningOCR) {
|
this.props.selectedAsset.asset.isRunningOCR !== prevProps.selectedAsset.asset.isRunningOCR ||
|
||||||
|
this.props.selectedAsset.asset.labelingState !== prevProps.selectedAsset.asset.labelingState
|
||||||
|
) {
|
||||||
this.selectedRegionIds = [];
|
this.selectedRegionIds = [];
|
||||||
this.imageMap.removeAllFeatures();
|
this.imageMap.removeAllFeatures();
|
||||||
this.imageMap.resetAllLayerVisibility();
|
this.imageMap.resetAllLayerVisibility();
|
||||||
|
@ -253,10 +258,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
handleAssetDeleted={this.props.onAssetDeleted}
|
handleAssetDeleted={this.props.onAssetDeleted}
|
||||||
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
|
handleRunOcrForAllDocuments={this.runOcrForAllDocuments}
|
||||||
handleRunAutoLabelingOnCurrentDocument={this.runAutoLabelingOnCurrentDocument}
|
handleRunAutoLabelingOnCurrentDocument={this.runAutoLabelingOnCurrentDocument}
|
||||||
connectionType={this.props.project.sourceConnection.providerType}
|
handleRunAutoLabelingForRestDocuments={this.runAutoLabelingForRestDocuments}
|
||||||
handleToggleDrawRegionMode={this.handleToggleDrawRegionMode}
|
handleToggleDrawRegionMode={this.handleToggleDrawRegionMode}
|
||||||
|
connectionType={this.props.project.sourceConnection.providerType}
|
||||||
drawRegionMode={this.state.drawRegionMode}
|
drawRegionMode={this.state.drawRegionMode}
|
||||||
project={this.props.project}
|
project={this.props.project}
|
||||||
|
selectedAsset={this.props.selectedAsset}
|
||||||
parentPage={strings.editorPage.title}
|
parentPage={strings.editorPage.title}
|
||||||
/>
|
/>
|
||||||
<ImageMap
|
<ImageMap
|
||||||
|
@ -267,6 +274,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
imageHeight={this.state.imageHeight}
|
imageHeight={this.state.imageHeight}
|
||||||
enableFeatureSelection={!this.state.drawRegionMode && !this.state.groupSelectMode}
|
enableFeatureSelection={!this.state.drawRegionMode && !this.state.groupSelectMode}
|
||||||
handleFeatureSelect={this.handleFeatureSelect}
|
handleFeatureSelect={this.handleFeatureSelect}
|
||||||
|
handleFeatureDoubleClick={this.handleFeatureDoubleClick}
|
||||||
featureStyler={this.featureStyler}
|
featureStyler={this.featureStyler}
|
||||||
groupSelectMode={this.state.groupSelectMode}
|
groupSelectMode={this.state.groupSelectMode}
|
||||||
handleIsPointerOnImage={this.handleIsPointerOnImage}
|
handleIsPointerOnImage={this.handleIsPointerOnImage}
|
||||||
|
@ -370,16 +378,19 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const assetPath = asset.path;
|
const assetPath = asset.path;
|
||||||
const predictService = new PredictService(this.props.project);
|
const predictService = new PredictService(this.props.project);
|
||||||
const result = await predictService.getPrediction(assetPath);
|
const result = await predictService.getPrediction(assetPath);
|
||||||
|
|
||||||
const assetService = new AssetService(this.props.project);
|
const assetService = new AssetService(this.props.project);
|
||||||
await assetService.uploadAssetPredictResult(asset, result);
|
const assetMetadata = assetService.getAssetPredictMetadata(asset, result);
|
||||||
const assetMetadata = await assetService.getAssetMetadata(asset);
|
|
||||||
await this.props.onAssetMetadataChanged(assetMetadata);
|
await this.props.onAssetMetadataChanged(assetMetadata);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
this.setAutoLabelingStatus(AutoLabelingStatus.done);
|
this.setAutoLabelingStatus(AutoLabelingStatus.done);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private runAutoLabelingForRestDocuments = async () => {
|
||||||
|
this.setState({ autoLableingStatus: AutoLabelingStatus.running });
|
||||||
|
await this.props.runAutoLabelingOnNextBatch();
|
||||||
|
this.setState({ autoLableingStatus: AutoLabelingStatus.done });
|
||||||
|
}
|
||||||
|
|
||||||
public updateSize() {
|
public updateSize() {
|
||||||
this.imageMap.updateSize();
|
this.imageMap.updateSize();
|
||||||
|
@ -557,7 +568,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const filteredRegions = this.state.currentAsset.regions.filter((assetRegion) => {
|
const filteredRegions = this.state.currentAsset.regions.filter((assetRegion) => {
|
||||||
return regions.findIndex((r) => r.id === assetRegion.id) === -1;
|
return regions.findIndex((r) => r.id === assetRegion.id) === -1;
|
||||||
});
|
});
|
||||||
this.updateAssetRegions(filteredRegions);
|
this.updateAssetRegions(filteredRegions, regions.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteRegionsFromImageMap = (regions: IRegion[]) => {
|
private deleteRegionsFromImageMap = (regions: IRegion[]) => {
|
||||||
|
@ -606,7 +617,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
* @param regions
|
* @param regions
|
||||||
* @param selectedRegions
|
* @param selectedRegions
|
||||||
*/
|
*/
|
||||||
private updateAssetRegions = (regions: IRegion[]) => {
|
private updateAssetRegions = (regions: IRegion[], manualOption: boolean = false) => {
|
||||||
const labelData = this.convertRegionsToLabelData(regions, this.state.currentAsset.asset.name);
|
const labelData = this.convertRegionsToLabelData(regions, this.state.currentAsset.asset.name);
|
||||||
console.log("Canvas -> privateupdateAssetRegions -> labelData", labelData)
|
console.log("Canvas -> privateupdateAssetRegions -> labelData", labelData)
|
||||||
const currentAsset: IAssetMetadata = {
|
const currentAsset: IAssetMetadata = {
|
||||||
|
@ -621,6 +632,41 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
(region) => region.tags[0] !== undefined &&
|
(region) => region.tags[0] !== undefined &&
|
||||||
region.pageNumber === this.state.currentPage));
|
region.pageNumber === this.state.currentPage));
|
||||||
}
|
}
|
||||||
|
if (manualOption) {
|
||||||
|
if (currentAsset.labelData) {
|
||||||
|
const labelingState = _.get(this.state, "currentAsset.labelData.labelingState", null);
|
||||||
|
if (labelingState) {
|
||||||
|
switch (labelingState) {
|
||||||
|
case AssetLabelingState.AutoLabeled:
|
||||||
|
case AssetLabelingState.AutoLabeledAndAdjusted:
|
||||||
|
currentAsset.labelData.labelingState = AssetLabelingState.AutoLabeledAndAdjusted;
|
||||||
|
break;
|
||||||
|
case AssetLabelingState.ManuallyLabeled:
|
||||||
|
case AssetLabelingState.Trained:
|
||||||
|
currentAsset.labelData.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
currentAsset.labelData.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentAsset.labelData.labelingState = AssetLabelingState.ManuallyLabeled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.state.currentAsset.labelData && currentAsset.labelData) {
|
||||||
|
currentAsset.labelData.labelingState = this.state.currentAsset.labelData.labelingState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentAsset.labelData) {
|
||||||
|
currentAsset.asset.labelingState = currentAsset.labelData.labelingState;
|
||||||
|
} else if (currentAsset.asset.labelingState) {
|
||||||
|
delete currentAsset.asset.labelingState;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
currentAsset,
|
currentAsset,
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -640,7 +686,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
const deletedRegionIndex = currentRegions.findIndex((region) => region.id === id);
|
const deletedRegionIndex = currentRegions.findIndex((region) => region.id === id);
|
||||||
currentRegions.splice(deletedRegionIndex, 1);
|
currentRegions.splice(deletedRegionIndex, 1);
|
||||||
|
|
||||||
this.updateAssetRegions(currentRegions);
|
this.updateAssetRegions(currentRegions, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -655,6 +701,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
this.props.onSelectedRegionsChanged(selectedRegions);
|
this.props.onSelectedRegionsChanged(selectedRegions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private onRegionDoubleClick = (id: string) => {
|
||||||
|
if (this.props.onRegionDoubleClick) {
|
||||||
|
const region = this.state.currentAsset.regions.find(region=>region.id === id);
|
||||||
|
this.props.onRegionDoubleClick(region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates regions in both Canvas Tools and the asset data store
|
* Updates regions in both Canvas Tools and the asset data store
|
||||||
|
@ -667,14 +719,14 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
for (const update of updates) {
|
for (const update of updates) {
|
||||||
const region = regions.find((r) => r.id === update.id);
|
const region = regions.find((r) => r.id === update.id);
|
||||||
if (region) {
|
if (region) {
|
||||||
// skip
|
region.changed = true;
|
||||||
} else {
|
} else {
|
||||||
updatedRegions.push(update);
|
updatedRegions.push(update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("Canvas -> privateupdateRegions -> updatedRegions", updatedRegions)
|
console.log("Canvas -> privateupdateRegions -> updatedRegions", updatedRegions)
|
||||||
updatedRegions.sort(this.compareRegionOrder);
|
updatedRegions.sort(this.compareRegionOrder);
|
||||||
this.updateAssetRegions(updatedRegions);
|
this.updateAssetRegions(updatedRegions, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createBoundingBoxVectorFeature = (text, boundingBox, imageExtent, ocrExtent, page) => {
|
private createBoundingBoxVectorFeature = (text, boundingBox, imageExtent, ocrExtent, page) => {
|
||||||
|
@ -1021,6 +1073,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
this.redrawAllFeatures();
|
this.redrawAllFeatures();
|
||||||
}
|
}
|
||||||
|
private handleFeatureDoubleClick = (feature: Feature, isToggle: boolean = true, category: FeatureCategory) => {
|
||||||
|
const regionId = feature.get("id");
|
||||||
|
if (this.isRegionSelected(regionId)) {
|
||||||
|
this.onRegionDoubleClick(regionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private handleMultiSelection = (regionId: any, category: FeatureCategory) => {
|
private handleMultiSelection = (regionId: any, category: FeatureCategory) => {
|
||||||
const selectedRegions = this.getSelectedRegions();
|
const selectedRegions = this.getSelectedRegions();
|
||||||
|
@ -1174,7 +1232,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, this.setOCRStatus, force);
|
const ocr = await this.ocrService.getRecognizedText(asset.path, asset.name, asset.mimeType, this.setOCRStatus, force);
|
||||||
if (asset.id === this.state.currentAsset.asset.id) {
|
if (asset.id === this.state.currentAsset.asset.id) {
|
||||||
// since get OCR is async, we only set currentAsset's OCR
|
// since get OCR is async, we only set currentAsset's OCR
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1340,6 +1398,13 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertRegionsToLabelData = (regions: IRegion[], assetName: string) => {
|
private convertRegionsToLabelData = (regions: IRegion[], assetName: string) => {
|
||||||
|
const labels = (this.props.selectedAsset
|
||||||
|
&& this.props.selectedAsset.labelData
|
||||||
|
&& this.props.selectedAsset.labelData.labels
|
||||||
|
&& this.props.selectedAsset.labelData.labels.map(label => ({
|
||||||
|
...label, value: []
|
||||||
|
}))) || [];
|
||||||
|
|
||||||
const labelData: ILabelData = {
|
const labelData: ILabelData = {
|
||||||
document: decodeURIComponent(assetName).split("/").pop(),
|
document: decodeURIComponent(assetName).split("/").pop(),
|
||||||
labels: [] as ILabel[],
|
labels: [] as ILabel[],
|
||||||
|
@ -1357,9 +1422,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
region.tags.forEach((tag) => {
|
region.tags.forEach((tag) => {
|
||||||
if (region.isTableRegion) {
|
if (region.isTableRegion) {
|
||||||
const tableRegion = region as ITableRegion;
|
const tableRegion = region as ITableRegion;
|
||||||
const tableLabel: ITableLabel = labelData.tableLabels.find((tableLabel) => { return tableLabel.tableKey === tag });
|
const tableLabel: ITableLabel = labelData.tableLabels.find((tableLabel) => tableLabel.tableKey === tag);
|
||||||
if (tableLabel) {
|
if (tableLabel) {
|
||||||
const tableLabelCell = tableLabel.labels.find((tableLabelCell) => { return tableLabelCell.columnKey === tableRegion.columnKey &&tableLabelCell.rowKey === tableRegion.rowKey });
|
const tableLabelCell = tableLabel.labels.find((tableLabelCell) => tableLabelCell.columnKey === tableRegion.columnKey && tableLabelCell.rowKey === tableRegion.rowKey);
|
||||||
if (tableLabelCell) {
|
if (tableLabelCell) {
|
||||||
tableLabelCell.value.push(formRegion)
|
tableLabelCell.value.push(formRegion)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1380,9 +1445,13 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
labels: [tableCellLabel]
|
labels: [tableCellLabel]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const label = labelData.labels.find((label) => { return label.label === tag });
|
const label = labelData.labels.find((label) => label.label === tag);
|
||||||
if (label) {
|
if (label) {
|
||||||
|
if (label.confidence && region.changed) {
|
||||||
|
delete label.confidence;
|
||||||
|
}
|
||||||
label.value.push(formRegion);
|
label.value.push(formRegion);
|
||||||
} else {
|
} else {
|
||||||
let newLabel;
|
let newLabel;
|
||||||
|
@ -1404,9 +1473,15 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
return labelData;
|
const newLabels = labelData.labels.filter(label => label.value.length > 0);
|
||||||
|
|
||||||
|
return newLabels.length > 0 || labelData.tableLabels.length > 0 ?
|
||||||
|
{
|
||||||
|
document: decodeURIComponent(assetName).split("/").pop(),
|
||||||
|
labels: newLabels,
|
||||||
|
tableLabels: labelData.tableLabels
|
||||||
|
} as ILabelData : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLabelType = (regionCategory: string) => {
|
private getLabelType = (regionCategory: string) => {
|
||||||
|
@ -1650,11 +1725,21 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
} else if (newLabels.length > 0) {
|
} else if (newLabels.length > 0) {
|
||||||
const newFieldNames = newLabels.map((label) => label.label);
|
const newFieldNames = newLabels.map((label) => label.label);
|
||||||
const prevFieldNames = prevLabels.map((label) => label.label);
|
const prevFieldNames = prevLabels.map((label) => label.label);
|
||||||
return !_.isEqual(newFieldNames.sort(), prevFieldNames.sort());
|
if (_.isEqual(newFieldNames.sort(), prevFieldNames.sort())) {
|
||||||
|
for (const name of newFieldNames) {
|
||||||
|
const newValue = newLabels.find(label => label.label === name).value.map(region => region.boundingBoxes).join(",");
|
||||||
|
const prevValue = prevLabels.find(label => label.label === name).value.map(region => region.boundingBoxes).join(",");
|
||||||
|
if (newValue !== prevValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getBoundingBoxTextFromRegion = (formRegion: IFormRegion, boundingBoxIndex: number) => {
|
private getBoundingBoxTextFromRegion = (formRegion: IFormRegion, boundingBoxIndex: number) => {
|
||||||
// get value from formRegion.text
|
// get value from formRegion.text
|
||||||
|
@ -1918,7 +2003,6 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const tag: ITag = this.props.project.tags.find((tag) => tag.name === tagName);
|
const tag: ITag = this.props.project.tags.find((tag) => tag.name === tagName);
|
||||||
|
|
||||||
let regionCategory: string;
|
let regionCategory: string;
|
||||||
if (labelType) {
|
if (labelType) {
|
||||||
regionCategory = labelType;
|
regionCategory = labelType;
|
||||||
|
@ -2057,11 +2141,17 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevTypes = {};
|
const prevTypes = {};
|
||||||
prevTags.forEach((tag) => prevTypes[tag.name] = tag.type);
|
const prevColors = {};
|
||||||
|
prevTags.forEach((tag) => {
|
||||||
|
prevTypes[tag.name] = tag.type;
|
||||||
|
prevColors[tag.name] = tag.color;
|
||||||
|
});
|
||||||
const types = {};
|
const types = {};
|
||||||
tags.forEach((tag) => types[tag.name] = tag.type);
|
const colors = {};
|
||||||
|
tags.forEach((tag) => {
|
||||||
|
types[tag.name] = tag.type;
|
||||||
|
colors[tag.name] = tag.color;
|
||||||
|
});
|
||||||
for (const name of names) {
|
for (const name of names) {
|
||||||
const prevType = prevTypes[name];
|
const prevType = prevTypes[name];
|
||||||
const type = types[name];
|
const type = types[name];
|
||||||
|
@ -2070,6 +2160,12 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
// some tag change between checkbox and text
|
// some tag change between checkbox and text
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
const prevColor = prevColors[name];
|
||||||
|
const color = colors[name];
|
||||||
|
if (prevColor !== color) {
|
||||||
|
// some tag color changed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -2125,6 +2221,9 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleToggleDrawRegionMode = () => {
|
private handleToggleDrawRegionMode = () => {
|
||||||
|
if (!this.state.drawRegionMode && this.props.project.apiVersion !== APIVersionPatches.patch3) {
|
||||||
|
toast.warn(interpolate(strings.editorPage.canvas.canvasCommandBar.warings.drawRegionUnsupportedAPIVersion, { apiVersion: (this.props.project.apiVersion || constants.appVersion ) }), {autoClose: 7000});
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
drawRegionMode: !this.state.drawRegionMode
|
drawRegionMode: !this.state.drawRegionMode
|
||||||
});
|
});
|
||||||
|
@ -2240,4 +2339,11 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
||||||
});
|
});
|
||||||
this.imageMap.modifyStartFeatureCoordinates = {};
|
this.imageMap.modifyStartFeatureCoordinates = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async focusOnLabel(label: ILabel) {
|
||||||
|
const { page } = label.value[ 0 ];
|
||||||
|
if (this.state.currentPage !== page) {
|
||||||
|
await this.goToPage(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,29 @@ import * as React from "react";
|
||||||
import { CommandBar, ICommandBarItemProps } from "@fluentui/react/lib/CommandBar";
|
import { CommandBar, ICommandBarItemProps } from "@fluentui/react/lib/CommandBar";
|
||||||
import { ICustomizations, Customizer } from "@fluentui/react/lib/Utilities";
|
import { ICustomizations, Customizer } from "@fluentui/react/lib/Utilities";
|
||||||
import { getDarkGreyTheme } from "../../../../common/themes";
|
import { getDarkGreyTheme } from "../../../../common/themes";
|
||||||
import { strings } from '../../../../common/strings';
|
import { interpolate, strings } from '../../../../common/strings';
|
||||||
import { ContextualMenuItemType } from "@fluentui/react";
|
import { ContextualMenuItemType } from "@fluentui/react";
|
||||||
import { IProject } from "../../../../models/applicationState";
|
import { IProject, IAssetMetadata, AssetLabelingState } from "../../../../models/applicationState";
|
||||||
|
import _ from "lodash";
|
||||||
import "./canvasCommandBar.scss";
|
import "./canvasCommandBar.scss";
|
||||||
|
import { constants } from "../../../../common/constants";
|
||||||
|
|
||||||
interface ICanvasCommandBarProps {
|
interface ICanvasCommandBarProps {
|
||||||
handleZoomIn: () => void;
|
handleZoomIn: () => void;
|
||||||
handleZoomOut: () => void;
|
handleZoomOut: () => void;
|
||||||
handleRunAutoLabelingOnCurrentDocument?: () => void;
|
|
||||||
project: IProject;
|
|
||||||
handleRotateImage: (degrees: number) => void;
|
|
||||||
handleRunOcr?: () => void;
|
handleRunOcr?: () => void;
|
||||||
handleRunOcrForAllDocuments?: () => void;
|
handleRunOcrForAllDocuments?: () => void;
|
||||||
|
handleRunAutoLabelingOnCurrentDocument?: () => void;
|
||||||
|
handleRunAutoLabelingForRestDocuments?: () => void;
|
||||||
handleLayerChange?: (layer: string) => void;
|
handleLayerChange?: (layer: string) => void;
|
||||||
handleToggleDrawRegionMode?: () => void;
|
handleToggleDrawRegionMode?: () => void;
|
||||||
|
handleAssetDeleted?: () => void;
|
||||||
|
project: IProject;
|
||||||
|
selectedAsset?: IAssetMetadata;
|
||||||
|
handleRotateImage: (degrees: number) => void;
|
||||||
|
|
||||||
drawRegionMode?: boolean;
|
drawRegionMode?: boolean;
|
||||||
connectionType?: string;
|
connectionType?: string;
|
||||||
handleAssetDeleted?: () => void;
|
|
||||||
layers?: any;
|
layers?: any;
|
||||||
parentPage: string;
|
parentPage: string;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +36,14 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
||||||
},
|
},
|
||||||
scopedSettings: {},
|
scopedSettings: {},
|
||||||
};
|
};
|
||||||
|
const disableAutoLabeling = !props.project.predictModelId;
|
||||||
|
let disableAutoLabelingCurrentAsset = disableAutoLabeling;
|
||||||
|
if (!disableAutoLabeling) {
|
||||||
|
const labelingState = _.get(props.selectedAsset, "labelData.labelingState");
|
||||||
|
if (labelingState === AssetLabelingState.ManuallyLabeled || labelingState === AssetLabelingState.Trained) {
|
||||||
|
disableAutoLabelingCurrentAsset = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let commandBarItems: ICommandBarItemProps[] = [];
|
let commandBarItems: ICommandBarItemProps[] = [];
|
||||||
if (props.parentPage === strings.editorPage.title) {
|
if (props.parentPage === strings.editorPage.title) {
|
||||||
|
@ -64,16 +77,16 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
||||||
isChecked: props.layers["checkboxes"],
|
isChecked: props.layers["checkboxes"],
|
||||||
onClick: () => props.handleLayerChange("checkboxes"),
|
onClick: () => props.handleLayerChange("checkboxes"),
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// key: "DrawnRegions",
|
key: "DrawnRegions",
|
||||||
// text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.drawnRegions,
|
text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.drawnRegions,
|
||||||
// canCheck: true,
|
canCheck: true,
|
||||||
// iconProps: { iconName: "AddField" },
|
iconProps: { iconName: "AddField" },
|
||||||
// isChecked: props.layers["drawnRegions"],
|
isChecked: props.layers["drawnRegions"],
|
||||||
// className: props.drawRegionMode ? "disabled" : "",
|
className: props.drawRegionMode ? "disabled" : "",
|
||||||
// onClick: () => props.handleLayerChange("drawnRegions"),
|
onClick: () => props.handleLayerChange("drawnRegions"),
|
||||||
// disabled: props.drawRegionMode
|
disabled: props.drawRegionMode
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
key: "Label",
|
key: "Label",
|
||||||
text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.labels,
|
text: strings.editorPage.canvas.canvasCommandBar.items.layers.subMenuItems.labels,
|
||||||
|
@ -85,16 +98,16 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// key: "drawRegion",
|
key: "drawRegion",
|
||||||
// text: strings.editorPage.canvas.canvasCommandBar.items.drawRegion,
|
text: strings.editorPage.canvas.canvasCommandBar.items.drawRegion,
|
||||||
// iconProps: { iconName: "AddField" },
|
iconProps: { iconName: "AddField" },
|
||||||
// toggle: true,
|
toggle: true,
|
||||||
// checked: props.drawRegionMode,
|
checked: props.drawRegionMode,
|
||||||
// className: !props.layers["drawnRegions"] ? "disabled" : "",
|
className: !props.layers["drawnRegions"] ? "disabled" : "",
|
||||||
// onClick: () => props.handleToggleDrawRegionMode(),
|
onClick: () => props.handleToggleDrawRegionMode(),
|
||||||
// disabled: !props.layers["drawnRegions"],
|
disabled: !props.layers["drawnRegions"],
|
||||||
// }
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,23 +168,34 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
||||||
key: "runOcrForCurrentDocument",
|
key: "runOcrForCurrentDocument",
|
||||||
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runOcrOnCurrentDocument,
|
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runOcrOnCurrentDocument,
|
||||||
iconProps: { iconName: "TextDocument" },
|
iconProps: { iconName: "TextDocument" },
|
||||||
onClick: () => props.handleRunOcr(),
|
onClick: () => { if (props.handleRunOcr) props.handleRunOcr(); },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "runOcrForAllDocuments",
|
key: "runOcrForAllDocuments",
|
||||||
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runOcrOnAllDocuments,
|
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runOcrOnAllDocuments,
|
||||||
iconProps: { iconName: "Documentation" },
|
iconProps: { iconName: "Documentation" },
|
||||||
onClick: () => props.handleRunOcrForAllDocuments(),
|
onClick: () => { if (props.handleRunOcrForAllDocuments) props.handleRunOcrForAllDocuments(); },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "runAutoLabelingCurrentDocument",
|
key: "runAutoLabelingCurrentDocument",
|
||||||
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingCurrentDocument,
|
text: strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingCurrentDocument,
|
||||||
iconProps: { iconName: "Tag" },
|
iconProps: { iconName: "Tag" },
|
||||||
disabled: !props.project.predictModelId,
|
disabled: disableAutoLabelingCurrentAsset,
|
||||||
title: props.project.predictModelId ? "" :
|
title: props.project.predictModelId ? "" :
|
||||||
strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.noPredictModelOnProject,
|
strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.noPredictModelOnProject,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
props.handleRunAutoLabelingOnCurrentDocument();
|
if (props.handleRunAutoLabelingOnCurrentDocument) props.handleRunAutoLabelingOnCurrentDocument();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "runAutoLabelingForRestDocuments",
|
||||||
|
text: interpolate(strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.runAutoLabelingOnNotLabelingDocuments, { batchSize: constants.autoLabelBatchSize }),
|
||||||
|
iconProps: { iconName: "Tag" },
|
||||||
|
disabled: disableAutoLabeling,
|
||||||
|
title: props.project.predictModelId ? "" :
|
||||||
|
strings.editorPage.canvas.canvasCommandBar.farItems.additionalActions.subIMenuItems.noPredictModelOnProject,
|
||||||
|
onClick: () => {
|
||||||
|
if (props.handleRunAutoLabelingForRestDocuments) props.handleRunAutoLabelingForRestDocuments();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -182,7 +206,7 @@ export const CanvasCommandBar: React.FunctionComponent<ICanvasCommandBarProps> =
|
||||||
key: "deleteAsset",
|
key: "deleteAsset",
|
||||||
text: strings.editorPage.asset.delete.title,
|
text: strings.editorPage.asset.delete.title,
|
||||||
iconProps: { iconName: "Delete" },
|
iconProps: { iconName: "Delete" },
|
||||||
onClick: () => props.handleAssetDeleted(),
|
onClick: () => { if (props.handleAssetDeleted) props.handleAssetDeleted(); },
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -232,6 +232,18 @@ canvas {
|
||||||
.badge-tagged {
|
.badge-tagged {
|
||||||
background-color: rgba(green, 0.9);
|
background-color: rgba(green, 0.9);
|
||||||
border: 1px solid $lighter-2;
|
border: 1px solid $lighter-2;
|
||||||
|
&-ManuallyLabeled{
|
||||||
|
background-color: rgba(rgb(128, 41, 0), 0.9);
|
||||||
|
}
|
||||||
|
&-Trained {
|
||||||
|
background-color: rgba(green, 0.9);
|
||||||
|
}
|
||||||
|
&-AutoLabeled {
|
||||||
|
background-color: rgba(rgb(136, 0, 91), 0.9);
|
||||||
|
}
|
||||||
|
&-AutoLabeledAndAdjusted{
|
||||||
|
background-color: rgba(rgb(0, 92, 128), 0.9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-visited {
|
.badge-visited {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { strings, interpolate } from "../../../../common/strings";
|
||||||
import {
|
import {
|
||||||
AssetState, AssetType, EditorMode, FieldType,
|
AssetState, AssetType, EditorMode, FieldType,
|
||||||
IApplicationState, IAppSettings, IAsset, IAssetMetadata,
|
IApplicationState, IAppSettings, IAsset, IAssetMetadata,
|
||||||
ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, TagInputMode,FieldFormat, ITableTag, ITableRegion
|
ILabel, IProject, IRegion, ISize, ITag, FeatureCategory, TagInputMode, FieldFormat, ITableTag, ITableRegion, AssetLabelingState
|
||||||
} from "../../../../models/applicationState";
|
} from "../../../../models/applicationState";
|
||||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||||
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
import IProjectActions, * as projectActions from "../../../../redux/actions/projectActions";
|
||||||
|
@ -89,6 +89,7 @@ export interface IEditorPageState {
|
||||||
hoveredLabel: ILabel;
|
hoveredLabel: ILabel;
|
||||||
/** Whether the task for loading all OCRs is running */
|
/** Whether the task for loading all OCRs is running */
|
||||||
isRunningOCRs?: boolean;
|
isRunningOCRs?: boolean;
|
||||||
|
isRunningAutoLabelings?: boolean;
|
||||||
/** Whether OCR is running in the main canvas */
|
/** Whether OCR is running in the main canvas */
|
||||||
isCanvasRunningOCR?: boolean;
|
isCanvasRunningOCR?: boolean;
|
||||||
isCanvasRunningAutoLabeling?: boolean;
|
isCanvasRunningAutoLabeling?: boolean;
|
||||||
|
@ -301,6 +302,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
onAssetMetadataChanged={this.onAssetMetadataChanged}
|
onAssetMetadataChanged={this.onAssetMetadataChanged}
|
||||||
onCanvasRendered={this.onCanvasRendered}
|
onCanvasRendered={this.onCanvasRendered}
|
||||||
onSelectedRegionsChanged={this.onSelectedRegionsChanged}
|
onSelectedRegionsChanged={this.onSelectedRegionsChanged}
|
||||||
|
onRegionDoubleClick={this.onRegionDoubleClick}
|
||||||
onRunningOCRStatusChanged={this.onCanvasRunningOCRStatusChanged}
|
onRunningOCRStatusChanged={this.onCanvasRunningOCRStatusChanged}
|
||||||
onRunningAutoLabelingStatusChanged={this.onCanvasRunningAutoLabelingStatusChanged}
|
onRunningAutoLabelingStatusChanged={this.onCanvasRunningAutoLabelingStatusChanged}
|
||||||
onTagChanged={this.onTagChanged}
|
onTagChanged={this.onTagChanged}
|
||||||
|
@ -312,6 +314,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
setTableToView={this.setTableToView}
|
setTableToView={this.setTableToView}
|
||||||
closeTableView={this.closeTableView}
|
closeTableView={this.closeTableView}
|
||||||
runOcrForAllDocs={this.loadOcrForNotVisited}
|
runOcrForAllDocs={this.loadOcrForNotVisited}
|
||||||
|
runAutoLabelingOnNextBatch={this.runAutoLabelingOnNextBatch}
|
||||||
appSettings={this.props.appSettings}
|
appSettings={this.props.appSettings}
|
||||||
handleLabelTable={this.handleLabelTable}
|
handleLabelTable={this.handleLabelTable}
|
||||||
>
|
>
|
||||||
|
@ -349,6 +352,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
splitPaneWidth={this.state.rightSplitPaneWidth}
|
splitPaneWidth={this.state.rightSplitPaneWidth}
|
||||||
reconfigureTableConfirm={this.reconfigureTableConfirm}
|
reconfigureTableConfirm={this.reconfigureTableConfirm}
|
||||||
addRowToDynamicTable={this.addRowToDynamicTable}
|
addRowToDynamicTable={this.addRowToDynamicTable}
|
||||||
|
onTagDoubleClick={this.onLabelDoubleClicked}
|
||||||
/>
|
/>
|
||||||
<Confirm
|
<Confirm
|
||||||
title={strings.editorPage.tags.rename.title}
|
title={strings.editorPage.tags.rename.title}
|
||||||
|
@ -710,30 +714,30 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
private onAssetMetadataChanged = async (assetMetadata: IAssetMetadata): Promise<void> => {
|
private onAssetMetadataChanged = async (assetMetadata: IAssetMetadata): Promise<void> => {
|
||||||
console.log("EditorPage -> assetMetadata", assetMetadata)
|
console.log("EditorPage -> assetMetadata", assetMetadata)
|
||||||
// Comment out below code as we allow regions without tags, it would make labeler's work easier.
|
// Comment out below code as we allow regions without tags, it would make labeler's work easier.
|
||||||
|
assetMetadata = JSON.parse(JSON.stringify(assetMetadata)); // alex
|
||||||
const initialState = assetMetadata.asset.state;
|
const initialState = assetMetadata.asset.state;
|
||||||
|
|
||||||
const asset = { ...assetMetadata.asset };
|
const asset = { ...assetMetadata.asset };
|
||||||
|
|
||||||
console.log("EditorPage -> asset", asset)
|
// console.log("EditorPage -> asset", asset)
|
||||||
if (this.isTaggableAssetType(assetMetadata.asset)) {
|
if (this.isTaggableAssetType(asset)) {
|
||||||
const hasLabels = _.get(assetMetadata, "labelData.labels.length", 0) > 0;
|
const hasLabels = _.get(assetMetadata, "labelData.labels.length", 0) > 0;
|
||||||
const hasTableLabels = _.get(assetMetadata, "labelData.tableLabels.length", 0) > 0
|
const hasTableLabels = _.get(assetMetadata, "labelData.tableLabels.length", 0) > 0;
|
||||||
assetMetadata.asset.state = hasLabels || hasTableLabels ?
|
asset.state = hasLabels || hasTableLabels ?
|
||||||
AssetState.Tagged :
|
AssetState.Tagged :
|
||||||
AssetState.Visited;
|
AssetState.Visited;
|
||||||
} else if (assetMetadata.asset.state === AssetState.NotVisited) {
|
} else if (asset.state === AssetState.NotVisited) {
|
||||||
assetMetadata.asset.state = AssetState.Visited;
|
asset.state = AssetState.Visited;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update asset metadata if state changes or is different
|
// Only update asset metadata if state changes or is different
|
||||||
if (initialState !== assetMetadata.asset.state || this.state.selectedAsset !== assetMetadata) {
|
if (initialState !== asset.state || this.state.selectedAsset !== assetMetadata) {
|
||||||
if (this.state.selectedAsset?.labelData?.labels && assetMetadata?.labelData?.labels &&
|
if (this.state.selectedAsset?.labelData?.labels && assetMetadata?.labelData?.labels && assetMetadata.labelData.labels.toString() !== this.state.selectedAsset.labelData.labels.toString()) {
|
||||||
assetMetadata.labelData.labels.toString() !== this.state.selectedAsset.labelData.labels.toString()) {
|
|
||||||
await this.updatedAssetMetadata(assetMetadata);
|
await this.updatedAssetMetadata(assetMetadata);
|
||||||
}
|
}
|
||||||
|
assetMetadata.asset = asset;
|
||||||
await this.props.actions.saveAssetMetadata(this.props.project, assetMetadata);
|
await this.props.actions.saveAssetMetadata(this.props.project, assetMetadata);
|
||||||
if (this.props.project.lastVisitedAssetId === assetMetadata.asset.id) {
|
if (this.props.project.lastVisitedAssetId === asset.id) {
|
||||||
this.setState({ selectedAsset: assetMetadata });
|
this.setState({ selectedAsset: assetMetadata });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -742,6 +746,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
// This forces the root assets that are displayed in the sidebar to
|
// This forces the root assets that are displayed in the sidebar to
|
||||||
// accurately show their correct state (not-visited, visited or tagged)
|
// accurately show their correct state (not-visited, visited or tagged)
|
||||||
const assets = [...this.state.assets];
|
const assets = [...this.state.assets];
|
||||||
|
// const asset = { ...assetMetadata.asset };
|
||||||
const assetIndex = assets.findIndex((a) => a.id === asset.id);
|
const assetIndex = assets.findIndex((a) => a.id === asset.id);
|
||||||
if (assetIndex > -1) {
|
if (assetIndex > -1) {
|
||||||
assets[assetIndex] = {
|
assets[assetIndex] = {
|
||||||
|
@ -783,6 +788,12 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
this.setState({ selectedRegions });
|
this.setState({ selectedRegions });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onRegionDoubleClick = (region: IRegion) => {
|
||||||
|
if (region.tags?.length > 0) {
|
||||||
|
this.tagInputRef.current.focusTag(region.tags[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private onTagsChanged = async (tags) => {
|
private onTagsChanged = async (tags) => {
|
||||||
const project = {
|
const project = {
|
||||||
...this.props.project,
|
...this.props.project,
|
||||||
|
@ -816,6 +827,9 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
if (this.state.isCanvasRunningAutoLabeling) {
|
if (this.state.isCanvasRunningAutoLabeling) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.state.isRunningAutoLabelings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const assetMetadata = await this.props.actions.loadAssetMetadata(this.props.project, asset);
|
const assetMetadata = await this.props.actions.loadAssetMetadata(this.props.project, asset);
|
||||||
|
|
||||||
|
@ -901,11 +915,11 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
const asset = this.state.assets.find((asset) => asset.id === assetId);
|
const asset = this.state.assets.find((asset) => asset.id === assetId);
|
||||||
if (asset && (asset.state === AssetState.NotVisited || runForAll)) {
|
if (asset && (asset.state === AssetState.NotVisited || runForAll)) {
|
||||||
try {
|
try {
|
||||||
this.updateAssetState(asset.id, true);
|
this.updateAssetState({ id: asset.id, isRunningOCR: true });
|
||||||
await ocrService.getRecognizedText(asset.path, asset.name, undefined, runForAll);
|
await ocrService.getRecognizedText(asset.path, asset.name, asset.mimeType, undefined, runForAll);
|
||||||
this.updateAssetState(asset.id, false, AssetState.Visited);
|
this.updateAssetState({ id: asset.id, isRunningOCR: false, assetState: AssetState.Visited });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.updateAssetState(asset.id, false);
|
this.updateAssetState({ id: asset.id, isRunningOCR: false });
|
||||||
this.setState({
|
this.setState({
|
||||||
isError: true,
|
isError: true,
|
||||||
errorTitle: err.title,
|
errorTitle: err.title,
|
||||||
|
@ -920,14 +934,75 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private runAutoLabelingOnNextBatch = async () => {
|
||||||
|
if (this.isBusy()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { project } = this.props;
|
||||||
|
const predictService = new PredictService(project);
|
||||||
|
const assetService = new AssetService(project);
|
||||||
|
|
||||||
private updateAssetState = (id: string, isRunningOCR: boolean, assetState?: AssetState) => {
|
if (this.state.assets) {
|
||||||
|
this.setState({ isRunningAutoLabelings: true });
|
||||||
|
const unlabeledAssetsBatch = [];
|
||||||
|
for (let i = 0; i < this.state.assets.length && unlabeledAssetsBatch.length < constants.autoLabelBatchSize; i++) {
|
||||||
|
const asset = this.state.assets[i];
|
||||||
|
if (asset.state === AssetState.NotVisited || asset.state === AssetState.Visited) {
|
||||||
|
unlabeledAssetsBatch.push(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await throttle(constants.maxConcurrentServiceRequests,
|
||||||
|
unlabeledAssetsBatch,
|
||||||
|
async (asset) => {
|
||||||
|
try {
|
||||||
|
this.updateAssetState({ id: asset.id, isRunningAutoLabeling: true });
|
||||||
|
const predictResult = await predictService.getPrediction(asset.path);
|
||||||
|
const assetMetadata = await assetService.getAssetPredictMetadata(asset, predictResult);
|
||||||
|
await assetService.uploadPredictResultAsOrcResult(asset, predictResult);
|
||||||
|
this.onAssetMetadataChanged(assetMetadata);
|
||||||
|
this.updateAssetState({
|
||||||
|
id: asset.id, isRunningAutoLabeling: false,
|
||||||
|
assetState: AssetState.Tagged,
|
||||||
|
labelingState: AssetLabelingState.AutoLabeled,
|
||||||
|
});
|
||||||
|
this.props.actions.updatedAssetMetadata(this.props.project, assetMetadata);
|
||||||
|
} catch (err) {
|
||||||
|
this.updateAssetState({ id: asset.id, isRunningOCR: false, isRunningAutoLabeling: false });
|
||||||
|
this.setState({
|
||||||
|
isError: true,
|
||||||
|
errorTitle: err.title,
|
||||||
|
errorMessage: err.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
this.setState({ isRunningAutoLabelings: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateAssetState = (newState: {
|
||||||
|
id: string,
|
||||||
|
isRunningOCR?: boolean,
|
||||||
|
isRunningAutoLabeling?: boolean,
|
||||||
|
assetState?: AssetState,
|
||||||
|
labelingState?: AssetLabelingState
|
||||||
|
}) => {
|
||||||
this.setState((state) => ({
|
this.setState((state) => ({
|
||||||
assets: state.assets.map((asset) => {
|
assets: state.assets.map((asset) => {
|
||||||
if (asset.id === id) {
|
if (asset.id === newState.id) {
|
||||||
const updatedAsset = { ...asset, isRunningOCR };
|
const updatedAsset = { ...asset, isRunningOCR: newState.isRunningOCR || false };
|
||||||
if (assetState !== undefined && asset.state === AssetState.NotVisited) {
|
if (newState.assetState !== undefined && asset.state === AssetState.NotVisited) {
|
||||||
updatedAsset.state = assetState;
|
updatedAsset.state = newState.assetState;
|
||||||
|
}
|
||||||
|
if (newState.labelingState) {
|
||||||
|
updatedAsset.labelingState = newState.labelingState;
|
||||||
|
}
|
||||||
|
if (newState.isRunningAutoLabeling !== undefined) {
|
||||||
|
updatedAsset.isRunningAutoLabeling = newState.isRunningAutoLabeling;
|
||||||
}
|
}
|
||||||
return updatedAsset;
|
return updatedAsset;
|
||||||
} else {
|
} else {
|
||||||
|
@ -935,8 +1010,8 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}), () => {
|
}), () => {
|
||||||
if (this.state.selectedAsset && id === this.state.selectedAsset.asset.id) {
|
const asset = this.state.assets.find((asset) => asset.id === newState.id);
|
||||||
const asset = this.state.assets.find((asset) => asset.id === id);
|
if (this.state.selectedAsset && newState.id === this.state.selectedAsset.asset.id) {
|
||||||
if (asset) {
|
if (asset) {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedAsset: { ...this.state.selectedAsset, asset: { ...asset } },
|
selectedAsset: { ...this.state.selectedAsset, asset: { ...asset } },
|
||||||
|
@ -953,24 +1028,56 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
|
||||||
const updatedAssets = [...this.state.assets];
|
const updatedAssets = [...this.state.assets];
|
||||||
let needUpdate = false;
|
let needUpdate = false;
|
||||||
updatedAssets.forEach((asset) => {
|
updatedAssets.forEach((asset) => {
|
||||||
const projectAsset = _.get(this.props, "project.assets[asset.id]", null);
|
const projectAsset = _.get(this.props, `project.assets[${asset.id}]`, null);
|
||||||
if (projectAsset) {
|
if (projectAsset) {
|
||||||
if (asset.state !== projectAsset.state) {
|
if (asset.state !== projectAsset.state || asset.labelingState !== projectAsset.labelingState) {
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
asset.state = projectAsset.state;
|
asset.state = projectAsset.state;
|
||||||
|
asset.labelingState = projectAsset.labelingState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (needUpdate) {
|
if (needUpdate) {
|
||||||
this.setState({ assets: updatedAssets });
|
this.setState({ assets: updatedAssets });
|
||||||
|
if (this.state.selectedAsset) {
|
||||||
|
const asset = this.state.selectedAsset.asset;
|
||||||
|
const currentAsset = _.get(this.props, `project.assets[${this.state.selectedAsset.asset.id}]`, null);
|
||||||
|
if (asset.state !== currentAsset.state || asset.labelingState !== currentAsset.labelingState) {
|
||||||
|
this.updateSelectAsset(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateSelectAsset = async (asset: IAsset) => {
|
||||||
|
const assetMetadata = await this.props.actions.loadAssetMetadata(this.props.project, asset);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!assetMetadata.asset.size) {
|
||||||
|
const assetProps = await HtmlFileReader.readAssetAttributes(asset);
|
||||||
|
assetMetadata.asset.size = { width: assetProps.width, height: assetProps.height };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("Error computing asset size");
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
tableToView: null,
|
||||||
|
tableToViewId: null,
|
||||||
|
selectedAsset: assetMetadata,
|
||||||
|
}, async () => {
|
||||||
|
await this.onAssetMetadataChanged(assetMetadata);
|
||||||
|
await this.props.actions.saveProject(this.props.project, false, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
private onLabelEnter = (label: ILabel) => {
|
private onLabelEnter = (label: ILabel) => {
|
||||||
this.setState({ hoveredLabel: label });
|
this.setState({ hoveredLabel: label });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onLabelDoubleClicked = (label:ILabel) =>{
|
||||||
|
this.canvas.current.focusOnLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
private onLabelLeave = (label: ILabel) => {
|
private onLabelLeave = (label: ILabel) => {
|
||||||
this.setState({ hoveredLabel: null });
|
this.setState({ hoveredLabel: null });
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AutoSizer, List } from "react-virtualized";
|
import { AutoSizer, List } from "react-virtualized";
|
||||||
import { FontIcon } from "@fluentui/react";
|
import { FontIcon } from "@fluentui/react";
|
||||||
import { IAsset, AssetState, ISize } from "../../../../models/applicationState";
|
import { IAsset, AssetState, ISize, AssetLabelingState } from "../../../../models/applicationState";
|
||||||
import { AssetPreview, ContentSource } from "../../common/assetPreview/assetPreview";
|
import { AssetPreview, ContentSource } from "../../common/assetPreview/assetPreview";
|
||||||
import { strings } from "../../../../common/strings";
|
import { strings } from "../../../../common/strings";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Properties for Editor Side Bar
|
* Properties for Editor Side Bar
|
||||||
|
@ -135,11 +136,14 @@ export default class EditorSideBar extends React.Component<IEditorSideBarProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderBadges = (asset: IAsset): JSX.Element => {
|
private renderBadges = (asset: IAsset): JSX.Element => {
|
||||||
|
const getBadgeTaggedClass = (state: AssetLabelingState): string => {
|
||||||
|
return state ? `badge-tagged-${AssetLabelingState[state]}` : "";
|
||||||
|
};
|
||||||
switch (asset.state) {
|
switch (asset.state) {
|
||||||
case AssetState.Tagged:
|
case AssetState.Tagged:
|
||||||
return (
|
return (
|
||||||
<span title={strings.editorPage.tagged}
|
<span title={_.startCase(AssetLabelingState[asset.labelingState])}
|
||||||
className="badge badge-tagged">
|
className={["badge", "badge-tagged", getBadgeTaggedClass(asset.labelingState)].join(" ")}>
|
||||||
<FontIcon iconName="Tag" />
|
<FontIcon iconName="Tag" />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -217,7 +217,16 @@ export default class HomePage extends React.Component<IHomePageProps, IHomePageS
|
||||||
}
|
}
|
||||||
|
|
||||||
private deleteProject = async (project: IProject) => {
|
private deleteProject = async (project: IProject) => {
|
||||||
|
try {
|
||||||
await this.props.actions.deleteProject(project);
|
await this.props.actions.deleteProject(project);
|
||||||
|
} catch (error) {
|
||||||
|
if(error instanceof AppError && error.errorCode === ErrorCode.SecurityTokenNotFound){
|
||||||
|
toast.error(error.message, {autoClose:false});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onProjectFileUpload = async (e, project) => {
|
private onProjectFileUpload = async (e, project) => {
|
||||||
|
|
|
@ -369,13 +369,13 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
||||||
if (model.attributes.isComposed) {
|
if (model.attributes.isComposed) {
|
||||||
const inclModels = model.composedTrainResults ?
|
const inclModels = model.composedTrainResults ?
|
||||||
model.composedTrainResults
|
model.composedTrainResults
|
||||||
: (await this.getModelByURl(constants.apiModelsPath + "/" + model.modelId)).composedTrainResults;
|
: (await this.getModelByURl(interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + model.modelId)).composedTrainResults;
|
||||||
|
|
||||||
for (const i of Object.keys(inclModels)) {
|
for (const i of Object.keys(inclModels)) {
|
||||||
let _model: IModel;
|
let _model: IModel;
|
||||||
let modelInfo: IComposedModelInfo;
|
let modelInfo: IComposedModelInfo;
|
||||||
try {
|
try {
|
||||||
_model = await this.getModelByURl(constants.apiModelsPath + "/" + inclModels[i].modelId);
|
_model = await this.getModelByURl(interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + inclModels[i].modelId);
|
||||||
modelInfo = {
|
modelInfo = {
|
||||||
id: _model.modelId,
|
id: _model.modelId,
|
||||||
name: _model.modelName,
|
name: _model.modelName,
|
||||||
|
@ -458,7 +458,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
||||||
private getRecentModels = async ():Promise<IModel[]> => {
|
private getRecentModels = async ():Promise<IModel[]> => {
|
||||||
const recentModelsList: IModel[] = [];
|
const recentModelsList: IModel[] = [];
|
||||||
const recentModelRequest = await allSettled(this.props.project.recentModelRecords.map(async (model) => {
|
const recentModelRequest = await allSettled(this.props.project.recentModelRecords.map(async (model) => {
|
||||||
return this.getModelByURl(constants.apiModelsPath + "/" + model.modelInfo.modelId);
|
return this.getModelByURl(interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + model.modelInfo.modelId);
|
||||||
}))
|
}))
|
||||||
recentModelRequest.forEach((recentModelRequest) => {
|
recentModelRequest.forEach((recentModelRequest) => {
|
||||||
if (recentModelRequest.status === "fulfilled") {
|
if (recentModelRequest.status === "fulfilled") {
|
||||||
|
@ -528,7 +528,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
||||||
private async getResponse(nextLink?: string) {
|
private async getResponse(nextLink?: string) {
|
||||||
const baseURL = nextLink === undefined ? url.resolve(
|
const baseURL = nextLink === undefined ? url.resolve(
|
||||||
this.props.project.apiUriBase,
|
this.props.project.apiUriBase,
|
||||||
constants.apiModelsPath,
|
interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }),
|
||||||
) : url.resolve(
|
) : url.resolve(
|
||||||
this.props.project.apiUriBase,
|
this.props.project.apiUriBase,
|
||||||
nextLink,
|
nextLink,
|
||||||
|
@ -734,7 +734,7 @@ export default class ModelComposePage extends React.Component<IModelComposePageP
|
||||||
modelName: name,
|
modelName: name,
|
||||||
};
|
};
|
||||||
|
|
||||||
const link = constants.apiModelsPath + "/compose";
|
const link = interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/compose";
|
||||||
const composeRes = await this.post(link, payload);
|
const composeRes = await this.post(link, payload);
|
||||||
const composedModel = await this.waitUntilModelIsReady(composeRes["headers"]["location"]);
|
const composedModel = await this.waitUntilModelIsReady(composeRes["headers"]["location"]);
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,16 @@ import React from 'react'
|
||||||
import './predictModelInfo.scss';
|
import './predictModelInfo.scss';
|
||||||
|
|
||||||
export default function PredictModelInfo({ modelInfo }) {
|
export default function PredictModelInfo({ modelInfo }) {
|
||||||
const { docType, modelId, docTypeConfidence } = modelInfo;
|
const { modelId, docTypeConfidence } = modelInfo;
|
||||||
return (
|
return (
|
||||||
<div className="model-info-container">
|
<div className="model-info-container">
|
||||||
<div className="model-info-item">
|
|
||||||
<span className="title" >docType:</span>
|
|
||||||
<span className="value" >{docType}</span>
|
|
||||||
</div>
|
|
||||||
<div className="model-info-item">
|
<div className="model-info-item">
|
||||||
<span className="title" >modelId:</span>
|
<span className="title" >modelId:</span>
|
||||||
<span className="value" >{modelId}</span>
|
<span className="value" >{modelId}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="model-info-item">
|
<div className="model-info-item">
|
||||||
<span className="title" >docTypeConfidence:</span>
|
<span className="title" >docTypeConfidence:</span>
|
||||||
<span className="value" >{docTypeConfidence}</span>
|
<span className="value" >{(docTypeConfidence * 100).toFixed(2) + "%"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -649,6 +649,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
handleRotateImage={this.handleRotateCanvas}
|
handleRotateImage={this.handleRotateCanvas}
|
||||||
project={this.props.project}
|
project={this.props.project}
|
||||||
parentPage={"predict"}
|
parentPage={"predict"}
|
||||||
|
layers={{}}
|
||||||
/>
|
/>
|
||||||
<ImageMap
|
<ImageMap
|
||||||
parentPage={ImageMapParent.Predict}
|
parentPage={ImageMapParent.Predict}
|
||||||
|
@ -767,7 +768,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
case "<model_id>":
|
case "<model_id>":
|
||||||
return modelID;
|
return modelID;
|
||||||
case "<API_version>":
|
case "<API_version>":
|
||||||
return constants.apiVersion;
|
return (this.props.project?.apiVersion || constants.apiVersion);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const fileURL = window.URL.createObjectURL(
|
const fileURL = window.URL.createObjectURL(
|
||||||
|
@ -811,7 +812,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
}
|
}
|
||||||
const endpointURL = url.resolve(
|
const endpointURL = url.resolve(
|
||||||
this.props.project.apiUriBase,
|
this.props.project.apiUriBase,
|
||||||
`${constants.apiModelsPath}/${modelID}/analyze?includeTextDetails=true`,
|
`${interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) })}/${modelID}/analyze?includeTextDetails=true`,
|
||||||
);
|
);
|
||||||
let headers;
|
let headers;
|
||||||
let body;
|
let body;
|
||||||
|
@ -1037,7 +1038,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
||||||
reject(_.get(
|
reject(_.get(
|
||||||
response,
|
response,
|
||||||
"data.analyzeResult.errors[0].errorMessage",
|
"data.analyzeResult.errors[0]",
|
||||||
"Generic error during prediction"));
|
"Generic error during prediction"));
|
||||||
} else if (Number(new Date()) < endTime) {
|
} else if (Number(new Date()) < endTime) {
|
||||||
// If the request isn't succeeded and the timeout hasn't elapsed, go again
|
// If the request isn't succeeded and the timeout hasn't elapsed, go again
|
||||||
|
@ -1075,7 +1076,6 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
}
|
}
|
||||||
private onAddAssetToProjectClick = async () => {
|
private onAddAssetToProjectClick = async () => {
|
||||||
if (this.state.file) {
|
if (this.state.file) {
|
||||||
// this.props.project.assets
|
|
||||||
const fileName = `${this.props.project.folderPath}/${decodeURIComponent(this.state.file.name)}`;
|
const fileName = `${this.props.project.folderPath}/${decodeURIComponent(this.state.file.name)}`;
|
||||||
const asset = Object.values(this.props.project.assets).find(asset => asset.name === fileName);
|
const asset = Object.values(this.props.project.assets).find(asset => asset.name === fileName);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
|
@ -1167,7 +1167,7 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
const modelID = this.props.project.predictModelId;
|
const modelID = this.props.project.predictModelId;
|
||||||
const endpointURL = url.resolve(
|
const endpointURL = url.resolve(
|
||||||
this.props.project.apiUriBase,
|
this.props.project.apiUriBase,
|
||||||
`${constants.apiModelsPath}/${modelID}`,
|
`${interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) })}/${modelID}`,
|
||||||
);
|
);
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -127,7 +127,7 @@ export default class PredictResult extends React.Component<IPredictResultProps,
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={"predictiontag-confidence"}>
|
<div className={"predictiontag-confidence"}>
|
||||||
<span>{item.confidence}</span>
|
<span>{(item.confidence * 100).toFixed(2)+"%" }</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,6 +26,11 @@
|
||||||
"description": "API key",
|
"description": "API key",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"apiVersion" : {
|
||||||
|
"title": "API version",
|
||||||
|
"description": "API version",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"title": "${strings.common.description}",
|
"title": "${strings.common.description}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
@ -31,6 +31,11 @@
|
||||||
"description": "API key",
|
"description": "API key",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"apiVersion" : {
|
||||||
|
"title": "API version",
|
||||||
|
"description": "API version",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"title": "${strings.common.description}",
|
"title": "${strings.common.description}",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { ProjectSettingAction } from "./projectSettingAction";
|
||||||
import { ProtectedInput } from "../../common/protectedInput/protectedInput";
|
import { ProtectedInput } from "../../common/protectedInput/protectedInput";
|
||||||
import { PrimaryButton } from "@fluentui/react";
|
import { PrimaryButton } from "@fluentui/react";
|
||||||
import { getPrimaryGreenTheme, getPrimaryGreyTheme } from "../../../../common/themes";
|
import { getPrimaryGreenTheme, getPrimaryGreyTheme } from "../../../../common/themes";
|
||||||
|
import { APIVersionPicker, IAPIVersionPickerProps } from "../../common/apiVersionPicker/apiVersionPicker";
|
||||||
|
|
||||||
// tslint:disable-next-line:no-var-requires
|
// tslint:disable-next-line:no-var-requires
|
||||||
const newFormSchema = addLocValues(require("./newProjectForm.json"));
|
const newFormSchema = addLocValues(require("./newProjectForm.json"));
|
||||||
|
@ -62,6 +63,7 @@ export interface IProjectFormState {
|
||||||
export default class ProjectForm extends React.Component<IProjectFormProps, IProjectFormState> {
|
export default class ProjectForm extends React.Component<IProjectFormProps, IProjectFormState> {
|
||||||
private widgets = {
|
private widgets = {
|
||||||
protectedInput: (ProtectedInput as any) as Widget,
|
protectedInput: (ProtectedInput as any) as Widget,
|
||||||
|
apiVersion: (APIVersionPicker as any) as Widget
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
|
@ -155,6 +157,11 @@ export default class ProjectForm extends React.Component<IProjectFormProps, IPro
|
||||||
onChange: props.onChange,
|
onChange: props.onChange,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
apiVersion: CustomField<IAPIVersionPickerProps>(APIVersionPicker, (props) => ({
|
||||||
|
id: props.idSchema.$id,
|
||||||
|
value: props.formData,
|
||||||
|
onChange: props.onChange,
|
||||||
|
})),
|
||||||
targetConnection: CustomField<IConnectionProviderPickerProps>(ConnectionPickerWithRouter, (props) => {
|
targetConnection: CustomField<IConnectionProviderPickerProps>(ConnectionPickerWithRouter, (props) => {
|
||||||
const targetConnections = this.props.connections
|
const targetConnections = this.props.connections
|
||||||
.filter((connection) => StorageProviderFactory.isRegistered(connection.providerType));
|
.filter((connection) => StorageProviderFactory.isRegistered(connection.providerType));
|
||||||
|
@ -209,6 +216,7 @@ export default class ProjectForm extends React.Component<IProjectFormProps, IPro
|
||||||
sourceConnection: args.formData.sourceConnection,
|
sourceConnection: args.formData.sourceConnection,
|
||||||
folderPath: this.normalizeFolderPath(args.formData.folderPath),
|
folderPath: this.normalizeFolderPath(args.formData.folderPath),
|
||||||
apiUriBase: args.formData.apiUriBase.trim(),
|
apiUriBase: args.formData.apiUriBase.trim(),
|
||||||
|
apiVersion: args.formData.apiVersion,
|
||||||
};
|
};
|
||||||
this.props.onSubmit(project);
|
this.props.onSubmit(project);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,8 @@
|
||||||
},
|
},
|
||||||
"apiKey": {
|
"apiKey": {
|
||||||
"ui:widget": "protectedInput"
|
"ui:widget": "protectedInput"
|
||||||
|
},
|
||||||
|
"apiVersion": {
|
||||||
|
"ui:widget": "apiVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,3 +80,18 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-saving {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
.project-saving-spinner {
|
||||||
|
margin: auto;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Redirect } from "react-router";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { bindActionCreators } from "redux";
|
import { bindActionCreators } from "redux";
|
||||||
import { RouteComponentProps } from "react-router-dom";
|
import { RouteComponentProps } from "react-router-dom";
|
||||||
import { FontIcon } from "@fluentui/react";
|
import { FontIcon, Label, Spinner, SpinnerSize } from "@fluentui/react";
|
||||||
import ProjectForm from "./projectForm";
|
import ProjectForm from "./projectForm";
|
||||||
import { constants } from "../../../../common/constants";
|
import { constants } from "../../../../common/constants";
|
||||||
import { strings, interpolate } from "../../../../common/strings";
|
import { strings, interpolate } from "../../../../common/strings";
|
||||||
|
@ -43,6 +43,7 @@ export interface IProjectSettingsPageState {
|
||||||
project: IProject;
|
project: IProject;
|
||||||
action: ProjectSettingAction;
|
action: ProjectSettingAction;
|
||||||
isError: boolean;
|
isError: boolean;
|
||||||
|
isCommiting: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: IApplicationState) {
|
function mapStateToProps(state: IApplicationState) {
|
||||||
|
@ -72,6 +73,7 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
||||||
project: this.props.project,
|
project: this.props.project,
|
||||||
action: null,
|
action: null,
|
||||||
isError: false,
|
isError: false,
|
||||||
|
isCommiting: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
|
@ -107,6 +109,11 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.state.project?.id) {
|
||||||
|
removeStorageItem(constants.projectFormTempKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Hide ProjectMetrics for private-preview
|
// Hide ProjectMetrics for private-preview
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
|
@ -132,6 +139,14 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
||||||
{this.state.isError &&
|
{this.state.isError &&
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
}
|
}
|
||||||
|
{this.state.isCommiting &&
|
||||||
|
<div className="project-saving">
|
||||||
|
<div className="project-saving-spinner">
|
||||||
|
<Label className="p-0" ></Label>
|
||||||
|
<Spinner size={SpinnerSize.large} label="Saving Project..." ariaLive="assertive" labelPosition="right" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -171,12 +186,14 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
||||||
private onFormChange = (project: IProject) => {
|
private onFormChange = (project: IProject) => {
|
||||||
if (this.isPartialProject(project)) {
|
if (this.isPartialProject(project)) {
|
||||||
setStorageItem(constants.projectFormTempKey, JSON.stringify(project));
|
setStorageItem(constants.projectFormTempKey, JSON.stringify(project));
|
||||||
|
this.setState({ project });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFormSubmit = async (project: IProject) => {
|
private onFormSubmit = async (project: IProject) => {
|
||||||
const isNew = !(!!project.id);
|
const isNew = !(!!project.id);
|
||||||
|
try {
|
||||||
|
this.setState({ isCommiting: true });
|
||||||
const projectService = new ProjectService();
|
const projectService = new ProjectService();
|
||||||
if (!(await projectService.isValidProjectConnection(project))) {
|
if (!(await projectService.isValidProjectConnection(project))) {
|
||||||
return;
|
return;
|
||||||
|
@ -190,7 +207,7 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
||||||
await this.deleteOldProjectWhenRenamed(project, isNew);
|
await this.deleteOldProjectWhenRenamed(project, isNew);
|
||||||
await this.props.applicationActions.ensureSecurityToken(project);
|
await this.props.applicationActions.ensureSecurityToken(project);
|
||||||
await this.props.projectActions.saveProject(project, false, true);
|
await this.props.projectActions.saveProject(project, false, true);
|
||||||
removeStorageItem(constants.projectFormTempKey);
|
// removeStorageItem(constants.projectFormTempKey);
|
||||||
|
|
||||||
toast.success(interpolate(strings.projectSettings.messages.saveSuccess, { project }));
|
toast.success(interpolate(strings.projectSettings.messages.saveSuccess, { project }));
|
||||||
|
|
||||||
|
@ -199,6 +216,10 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
||||||
} else {
|
} else {
|
||||||
this.props.history.goBack();
|
this.props.history.goBack();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
this.setState({ isCommiting: false });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFormCancel = () => {
|
private onFormCancel = () => {
|
||||||
|
@ -210,7 +231,7 @@ export default class ProjectSettingsPage extends React.Component<IProjectSetting
|
||||||
* Checks whether a project is partially populated
|
* Checks whether a project is partially populated
|
||||||
*/
|
*/
|
||||||
private isPartialProject = (project: IProject): boolean => {
|
private isPartialProject = (project: IProject): boolean => {
|
||||||
return project && !(!!project.id) &&
|
return project &&
|
||||||
(
|
(
|
||||||
!!project.name
|
!!project.name
|
||||||
|| !!project.description
|
|| !!project.description
|
||||||
|
|
|
@ -10,7 +10,7 @@ import IProjectActions, * as projectActions from "../../../../redux/actions/proj
|
||||||
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
import IApplicationActions, * as applicationActions from "../../../../redux/actions/applicationActions";
|
||||||
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
import IAppTitleActions, * as appTitleActions from "../../../../redux/actions/appTitleActions";
|
||||||
import {
|
import {
|
||||||
IApplicationState, IConnection, IProject, IAppSettings, FieldType, IRecentModel,
|
IApplicationState, IConnection, IProject, IAppSettings, FieldType, IRecentModel, AssetLabelingState,
|
||||||
} from "../../../../models/applicationState";
|
} from "../../../../models/applicationState";
|
||||||
import TrainChart from "./trainChart";
|
import TrainChart from "./trainChart";
|
||||||
import TrainPanel from "./trainPanel";
|
import TrainPanel from "./trainPanel";
|
||||||
|
@ -26,6 +26,8 @@ import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
||||||
import ServiceHelper from "../../../../services/serviceHelper";
|
import ServiceHelper from "../../../../services/serviceHelper";
|
||||||
import { getPrimaryGreenTheme, getGreenWithWhiteBackgroundTheme } from "../../../../common/themes";
|
import { getPrimaryGreenTheme, getGreenWithWhiteBackgroundTheme } from "../../../../common/themes";
|
||||||
import { getAppInsights } from '../../../../services/telemetryService';
|
import { getAppInsights } from '../../../../services/telemetryService';
|
||||||
|
import { AssetService } from "../../../../services/assetService";
|
||||||
|
import Confirm from "../../common/confirm/confirm";
|
||||||
import UseLocalStorage from '../../../../services/useLocalStorage';
|
import UseLocalStorage from '../../../../services/useLocalStorage';
|
||||||
import { isElectron } from "../../../../common/hostProcess";
|
import { isElectron } from "../../../../common/hostProcess";
|
||||||
|
|
||||||
|
@ -80,6 +82,7 @@ function mapDispatchToProps(dispatch) {
|
||||||
@connect(mapStateToProps, mapDispatchToProps)
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
export default class TrainPage extends React.Component<ITrainPageProps, ITrainPageState> {
|
export default class TrainPage extends React.Component<ITrainPageProps, ITrainPageState> {
|
||||||
private appInsights: any = null;
|
private appInsights: any = null;
|
||||||
|
private notAdjustedLabelsConfirm: React.RefObject<Confirm> = React.createRef();
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -123,6 +126,8 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
||||||
const currTrainRecord = this.state.currTrainRecord;
|
const currTrainRecord = this.state.currTrainRecord;
|
||||||
const localFileSystemProvider: boolean = this.props.project && this.props.project.sourceConnection &&
|
const localFileSystemProvider: boolean = this.props.project && this.props.project.sourceConnection &&
|
||||||
this.props.project.sourceConnection.providerType === "localFileSystemProxy";
|
this.props.project.sourceConnection.providerType === "localFileSystemProxy";
|
||||||
|
const trainDisabled: boolean = localFileSystemProvider && (this.state.inputtedLabelFolderURL.length === 0 ||
|
||||||
|
this.state.inputtedLabelFolderURL === strings.train.defaultLabelFolderURL);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="train-page skipToMainContent" id="pageTrain">
|
<div className="train-page skipToMainContent" id="pageTrain">
|
||||||
|
@ -221,7 +226,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="flex-center"
|
className="flex-center"
|
||||||
onClick={this.handleDownloadJSONClick}
|
onClick={this.handleDownloadJSONClick}
|
||||||
disabled={this.state.isTraining}>
|
disabled={trainDisabled}>
|
||||||
<FontIcon
|
<FontIcon
|
||||||
iconName="Download"
|
iconName="Download"
|
||||||
style={{ fontWeight: 600 }} />
|
style={{ fontWeight: 600 }} />
|
||||||
|
@ -244,6 +249,13 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
||||||
when={this.state.isTraining}
|
when={this.state.isTraining}
|
||||||
message={"A training operation is currently in progress, are you sure you want to leave?"}
|
message={"A training operation is currently in progress, are you sure you want to leave?"}
|
||||||
/>
|
/>
|
||||||
|
<Confirm
|
||||||
|
ref={this.notAdjustedLabelsConfirm}
|
||||||
|
title={strings.train.trainConfirm.title}
|
||||||
|
message={strings.train.trainConfirm.message}
|
||||||
|
onConfirm={this.handleModelTrainConfirm}
|
||||||
|
confirmButtonTheme={getPrimaryGreenTheme()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -284,18 +296,43 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrainClick = () => {
|
private handleTrainClick = () => {
|
||||||
|
const assets = Object.values(this.props.project.assets)
|
||||||
|
.filter(asset => asset.labelingState === AssetLabelingState.AutoLabeled);
|
||||||
|
if (assets.length > 0) {
|
||||||
|
this.notAdjustedLabelsConfirm.current.open();
|
||||||
|
} else {
|
||||||
|
this.handleModelTrain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleModelTrainConfirm = () => {
|
||||||
|
this.handleModelTrain();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleModelTrain = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isTraining: true,
|
isTraining: true,
|
||||||
trainMessage: strings.train.training,
|
trainMessage: strings.train.training,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.trainProcess().then((trainResult) => {
|
this.trainProcess().then(async (trainResult) => {
|
||||||
this.setState((prevState, props) => ({
|
this.setState((prevState, props) => ({
|
||||||
isTraining: false,
|
isTraining: false,
|
||||||
trainMessage: this.getTrainMessage(trainResult),
|
trainMessage: this.getTrainMessage(trainResult),
|
||||||
currTrainRecord: this.getProjectTrainRecord(),
|
currTrainRecord: this.getProjectTrainRecord(),
|
||||||
modelName: "",
|
modelName: "",
|
||||||
}));
|
}));
|
||||||
|
const assets = Object.values(this.props.project.assets);
|
||||||
|
const assetService = new AssetService(this.props.project);
|
||||||
|
for (const asset of assets) {
|
||||||
|
const newAsset = JSON.parse(JSON.stringify(asset));
|
||||||
|
newAsset.labelingState = AssetLabelingState.Trained;
|
||||||
|
const metadata = await assetService.getAssetMetadata(newAsset);
|
||||||
|
if (metadata.labelData && metadata.labelData.labelingState !== AssetLabelingState.Trained) {
|
||||||
|
metadata.labelData.labelingState = AssetLabelingState.Trained;
|
||||||
|
await assetService.save({ ...metadata });
|
||||||
|
}
|
||||||
|
}
|
||||||
// reset localStorage successful train process
|
// reset localStorage successful train process
|
||||||
localStorage.setItem("trainPage_inputs", "{}");
|
localStorage.setItem("trainPage_inputs", "{}");
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
@ -338,7 +375,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
||||||
private async train(): Promise<any> {
|
private async train(): Promise<any> {
|
||||||
const baseURL = url.resolve(
|
const baseURL = url.resolve(
|
||||||
this.props.project.apiUriBase,
|
this.props.project.apiUriBase,
|
||||||
constants.apiModelsPath,
|
interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }),
|
||||||
);
|
);
|
||||||
const provider = this.props.project.sourceConnection.providerOptions as any;
|
const provider = this.props.project.sourceConnection.providerOptions as any;
|
||||||
let trainSourceURL;
|
let trainSourceURL;
|
||||||
|
@ -487,7 +524,7 @@ export default class TrainPage extends React.Component<ITrainPageProps, ITrainPa
|
||||||
}
|
}
|
||||||
|
|
||||||
private async triggerJsonDownload(): Promise<any> {
|
private async triggerJsonDownload(): Promise<any> {
|
||||||
const currModelUrl = this.props.project.apiUriBase + constants.apiModelsPath + "/" + this.state.currTrainRecord.modelInfo.modelId;
|
const currModelUrl = this.props.project.apiUriBase + interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) }) + "/" + this.state.currTrainRecord.modelInfo.modelId;
|
||||||
const modelUrl = this.state.modelUrl.length ? this.state.modelUrl : currModelUrl;
|
const modelUrl = this.state.modelUrl.length ? this.state.modelUrl : currModelUrl;
|
||||||
const modelJSON = await this.getModelsJson(this.props.project, modelUrl);
|
const modelJSON = await this.getModelsJson(this.props.project, modelUrl);
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default class TrainRecord extends React.Component<ITrainRecordProps, ITra
|
||||||
</p>
|
</p>
|
||||||
<h6>Average accuracy:</h6>
|
<h6>Average accuracy:</h6>
|
||||||
<p>
|
<p>
|
||||||
{this.props.averageAccuracy}
|
{(this.props.averageAccuracy * 100).toFixed(2)+"%"}
|
||||||
</p>
|
</p>
|
||||||
<div className="accuracy-info">
|
<div className="accuracy-info">
|
||||||
<a href="https://aka.ms/form-recognizer/docs/train" target="_blank" rel="noopener noreferrer">
|
<a href="https://aka.ms/form-recognizer/docs/train" target="_blank" rel="noopener noreferrer">
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default class TrainTable
|
||||||
Object.entries(this.props.accuracies).map((entry) =>
|
Object.entries(this.props.accuracies).map((entry) =>
|
||||||
<tr key={entry[0]}>
|
<tr key={entry[0]}>
|
||||||
<td>{entry[0]}</td>
|
<td>{entry[0]}</td>
|
||||||
<td className="text-right">{entry[1]}</td>
|
<td className="text-right">{(entry[1] * 100).toFixed(2) + "%"}</td>
|
||||||
</tr>)
|
</tr>)
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe("StatusBar component", () => {
|
||||||
|
|
||||||
function createComponent() {
|
function createComponent() {
|
||||||
return mount(
|
return mount(
|
||||||
<StatusBar>
|
<StatusBar project={undefined}>
|
||||||
<div className="child-component">Child Component</div>
|
<div className="child-component">Child Component</div>
|
||||||
</StatusBar>,
|
</StatusBar>,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,18 +5,31 @@ import React from "react";
|
||||||
import { FontIcon } from "@fluentui/react";
|
import { FontIcon } from "@fluentui/react";
|
||||||
import { constants } from "../../../common/constants";
|
import { constants } from "../../../common/constants";
|
||||||
import "./statusBar.scss";
|
import "./statusBar.scss";
|
||||||
|
import { IProject } from "../../../models/applicationState";
|
||||||
|
|
||||||
export class StatusBar extends React.Component {
|
export interface IStatusBarProps {
|
||||||
|
project: IProject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StatusBar extends React.Component<IStatusBarProps> {
|
||||||
public render() {
|
public render() {
|
||||||
return (
|
return (
|
||||||
<div className="status-bar">
|
<div className="status-bar">
|
||||||
<div className="status-bar-main">{this.props.children}</div>
|
<div className="status-bar-main">{this.props.children}</div>
|
||||||
<div className="status-bar-version">
|
<div className="status-bar-version">
|
||||||
<ul>
|
<ul>
|
||||||
|
{this.props.project &&
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/microsoft/OCR-Form-Tools/blob/master/CHANGELOG.md" target="blank" rel="noopener noreferrer">
|
||||||
|
<FontIcon iconName="AzureAPIManagement" />
|
||||||
|
<span>{ this.props.project.apiVersion || constants.apiVersion }</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/microsoft/OCR-Form-Tools/blob/master/CHANGELOG.md" target="blank" rel="noopener noreferrer">
|
<a href="https://github.com/microsoft/OCR-Form-Tools/blob/master/CHANGELOG.md" target="blank" rel="noopener noreferrer">
|
||||||
<FontIcon iconName="BranchMerge" />
|
<FontIcon iconName="BranchMerge" />
|
||||||
<span>{constants.appVersion}-b92b73b</span>
|
<span>{constants.appVersionRaw}-1f33130</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -152,11 +152,11 @@ export function deleteProject(project: IProject)
|
||||||
.find((securityToken) => securityToken.name === project.securityToken);
|
.find((securityToken) => securityToken.name === project.securityToken);
|
||||||
|
|
||||||
if (!projectToken) {
|
if (!projectToken) {
|
||||||
throw new AppError(ErrorCode.SecurityTokenNotFound, "Security Token Not Found");
|
dispatch(deleteProjectAction(project));
|
||||||
|
throw new AppError(ErrorCode.SecurityTokenNotFound, interpolate(strings.errors.projectDeleteErrorSecurityTokenNotFound.message, {project}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptedProject = await projectService.load(project, projectToken);
|
const decryptedProject = await projectService.load(project, projectToken);
|
||||||
|
|
||||||
await projectService.delete(decryptedProject);
|
await projectService.delete(decryptedProject);
|
||||||
dispatch(deleteProjectAction(decryptedProject));
|
dispatch(deleteProjectAction(decryptedProject));
|
||||||
};
|
};
|
||||||
|
@ -181,7 +181,7 @@ export function addAssetToProject(project: IProject, fileName: string, buffer: B
|
||||||
const assetName = project.folderPath ? `${project.folderPath}/${fileName}` : fileName;
|
const assetName = project.folderPath ? `${project.folderPath}/${fileName}` : fileName;
|
||||||
const asset = assets.find(a => a.name === assetName);
|
const asset = assets.find(a => a.name === assetName);
|
||||||
|
|
||||||
await assetService.uploadAssetPredictResult(asset, analyzeResult);
|
await assetService.syncAssetPredictResult(asset, analyzeResult);
|
||||||
dispatch(addAssetToProjectAction(asset));
|
dispatch(addAssetToProjectAction(asset));
|
||||||
return asset;
|
return asset;
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,9 +38,9 @@ export const reducer = (state: IProject = null, action: AnyAction): IProject =>
|
||||||
};
|
};
|
||||||
case ActionTypes.DELETE_PROJECT_ASSET_SUCCESS:
|
case ActionTypes.DELETE_PROJECT_ASSET_SUCCESS:
|
||||||
case ActionTypes.LOAD_PROJECT_ASSETS_SUCCESS:
|
case ActionTypes.LOAD_PROJECT_ASSETS_SUCCESS:
|
||||||
const assets = {};
|
let assets = {};
|
||||||
action.payload.forEach((asset) => {
|
action.payload.forEach((asset) => {
|
||||||
assets[asset.id] = asset;
|
assets = { ...assets, [asset.id]: { ...asset } };
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -22,6 +22,7 @@ export const reducer = (state: IProject[] = [], action: AnyAction): IProject[] =
|
||||||
let newState: IProject[] = null;
|
let newState: IProject[] = null;
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case ActionTypes.LOAD_PROJECT_SUCCESS:
|
||||||
case ActionTypes.SAVE_PROJECT_SUCCESS:
|
case ActionTypes.SAVE_PROJECT_SUCCESS:
|
||||||
return [
|
return [
|
||||||
{ ...action.payload },
|
{ ...action.payload },
|
||||||
|
@ -38,6 +39,9 @@ export const reducer = (state: IProject[] = [], action: AnyAction): IProject[] =
|
||||||
return updatedProject;
|
return updatedProject;
|
||||||
});
|
});
|
||||||
return newState;
|
return newState;
|
||||||
|
case ActionTypes.UPDATE_TAG_LABEL_COUNTS_SUCCESS:
|
||||||
|
return [{ ...action.payload },
|
||||||
|
...state.filter(project => project.id !== action.payload.id)];
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,6 @@ export function registerIcons() {
|
||||||
RectangleShape: "\uF1A9",
|
RectangleShape: "\uF1A9",
|
||||||
Rotate90CounterClockwise: "\uF80E",
|
Rotate90CounterClockwise: "\uF80E",
|
||||||
Rotate90Clockwise: "\uF80D",
|
Rotate90Clockwise: "\uF80D",
|
||||||
},
|
AzureAPIManagement: "\uF37F", },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import _ from "lodash";
|
||||||
import Guard from "../common/guard";
|
import Guard from "../common/guard";
|
||||||
import {
|
import {
|
||||||
IAsset, AssetType, IProject, IAssetMetadata, AssetState,
|
IAsset, AssetType, IProject, IAssetMetadata, AssetState,
|
||||||
ILabelData, ILabel,
|
ILabelData, ILabel, AssetLabelingState
|
||||||
} from "../models/applicationState";
|
} from "../models/applicationState";
|
||||||
import { AssetProviderFactory, IAssetProvider } from "../providers/storage/assetProviderFactory";
|
import { AssetProviderFactory, IAssetProvider } from "../providers/storage/assetProviderFactory";
|
||||||
import { StorageProviderFactory, IStorageProvider } from "../providers/storage/storageProviderFactory";
|
import { StorageProviderFactory, IStorageProvider } from "../providers/storage/storageProviderFactory";
|
||||||
|
@ -16,6 +16,9 @@ import { strings, interpolate } from "../common/strings";
|
||||||
import { sha256Hash } from "../common/crypto";
|
import { sha256Hash } from "../common/crypto";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import allSettled from "promise.allsettled"
|
import allSettled from "promise.allsettled"
|
||||||
|
import mime from 'mime';
|
||||||
|
import FileType from 'file-type';
|
||||||
|
import BrowserFileType from 'file-type/browser';
|
||||||
|
|
||||||
const supportedImageFormats = {
|
const supportedImageFormats = {
|
||||||
jpg: null, jpeg: null, null: null, png: null, bmp: null, tif: null, tiff: null, pdf: null,
|
jpg: null, jpeg: null, null: null, png: null, bmp: null, tif: null, tiff: null, pdf: null,
|
||||||
|
@ -65,9 +68,10 @@ export class AssetService {
|
||||||
private getOcrFromAnalyzeResult(analyzeResult: any) {
|
private getOcrFromAnalyzeResult(analyzeResult: any) {
|
||||||
return _.get(analyzeResult, "analyzeResult.readResults", []);
|
return _.get(analyzeResult, "analyzeResult.readResults", []);
|
||||||
}
|
}
|
||||||
async uploadAssetPredictResult(asset: IAsset, readResults: any): Promise<void> {
|
getAssetPredictMetadata(asset: IAsset, predictResults: any) {
|
||||||
|
asset = JSON.parse(JSON.stringify(asset));
|
||||||
const getBoundingBox = (pageIndex, arr: number[]) => {
|
const getBoundingBox = (pageIndex, arr: number[]) => {
|
||||||
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(readResults)[pageIndex - 1];
|
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(predictResults)[pageIndex - 1];
|
||||||
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
||||||
const ocrWidth = ocrExtent[2] - ocrExtent[0];
|
const ocrWidth = ocrExtent[2] - ocrExtent[0];
|
||||||
const ocrHeight = ocrExtent[3] - ocrExtent[1];
|
const ocrHeight = ocrExtent[3] - ocrExtent[1];
|
||||||
|
@ -83,7 +87,7 @@ export class AssetService {
|
||||||
const getLabelValues = (field: any) => {
|
const getLabelValues = (field: any) => {
|
||||||
return field.elements.map((path: string) => {
|
return field.elements.map((path: string) => {
|
||||||
const pathArr = path.split('/').slice(1);
|
const pathArr = path.split('/').slice(1);
|
||||||
const word = pathArr.reduce((obj: any, key: string) => obj[key], { ...readResults.analyzeResult });
|
const word = pathArr.reduce((obj: any, key: string) => obj[key], { ...predictResults.analyzeResult });
|
||||||
return {
|
return {
|
||||||
page: field.page,
|
page: field.page,
|
||||||
text: word.text || word.state,
|
text: word.text || word.state,
|
||||||
|
@ -92,59 +96,76 @@ export class AssetService {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const labels = [];
|
const labels =
|
||||||
readResults.analyzeResult.documentResults
|
predictResults.analyzeResult.documentResults
|
||||||
.map(result => Object.keys(result.fields)
|
.map(result => Object.keys(result.fields)
|
||||||
.filter(key => result.fields[key])
|
.filter(key => result.fields[key])
|
||||||
.map<ILabel>(key => (
|
.map<ILabel>(key => (
|
||||||
{
|
{
|
||||||
label: key,
|
label: key,
|
||||||
key: null,
|
key: null,
|
||||||
|
confidence: result.fields[key].confidence,
|
||||||
value: getLabelValues(result.fields[key])
|
value: getLabelValues(result.fields[key])
|
||||||
}))).forEach(items => {
|
}))).flat(2);
|
||||||
labels.push(...items);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (labels.length > 0) {
|
if (labels.length > 0) {
|
||||||
const fileName = decodeURIComponent(asset.name).split('/').pop();
|
const fileName = decodeURIComponent(asset.name).split('/').pop();
|
||||||
const labelData: ILabelData = {
|
const labelData: ILabelData = {
|
||||||
document: fileName,
|
document: fileName,
|
||||||
|
labelingState: AssetLabelingState.AutoLabeled,
|
||||||
labels
|
labels
|
||||||
};
|
};
|
||||||
const metadata = {
|
const metadata: IAssetMetadata = {
|
||||||
...await this.getAssetMetadata(asset),
|
asset: { ...asset, labelingState: AssetLabelingState.AutoLabeled },
|
||||||
labelData
|
regions: [],
|
||||||
|
version: appInfo.version,
|
||||||
|
labelData,
|
||||||
};
|
};
|
||||||
metadata.asset.state = AssetState.Tagged;
|
metadata.asset.state = AssetState.Tagged;
|
||||||
|
return metadata;
|
||||||
const ocrData = JSON.parse(JSON.stringify(readResults));
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async uploadPredictResultAsOrcResult(asset: IAsset, predictResults: any): Promise<void> {
|
||||||
|
const ocrData = JSON.parse(JSON.stringify(predictResults));
|
||||||
delete ocrData.analyzeResult.documentResults;
|
delete ocrData.analyzeResult.documentResults;
|
||||||
if (ocrData.analyzeResult.errors) {
|
if (ocrData.analyzeResult.errors) {
|
||||||
delete ocrData.analyzeResult.errors;
|
delete ocrData.analyzeResult.errors;
|
||||||
}
|
}
|
||||||
const ocrFileName = `${asset.name}${constants.ocrFileExtension}`;
|
const ocrFileName = `${asset.name}${constants.ocrFileExtension}`;
|
||||||
await Promise.all([
|
await this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2));
|
||||||
this.save(metadata),
|
|
||||||
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
const ocrData = { ...readResults };
|
async syncAssetPredictResult(asset: IAsset, predictResults: any): Promise<IAssetMetadata> {
|
||||||
|
const assetMeatadata = this.getAssetPredictMetadata(asset, predictResults);
|
||||||
|
const ocrData = JSON.parse(JSON.stringify(predictResults));
|
||||||
delete ocrData.analyzeResult.documentResults;
|
delete ocrData.analyzeResult.documentResults;
|
||||||
if (ocrData.analyzeResult.errors) {
|
if (ocrData.analyzeResult.errors) {
|
||||||
delete ocrData.analyzeResult.errors;
|
delete ocrData.analyzeResult.errors;
|
||||||
}
|
}
|
||||||
|
const ocrFileName = `${asset.name}${constants.ocrFileExtension}`;
|
||||||
|
if (assetMeatadata) {
|
||||||
|
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.save(assetMeatadata),
|
||||||
|
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
||||||
|
]);
|
||||||
|
return assetMeatadata;
|
||||||
|
}
|
||||||
|
else {
|
||||||
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
|
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
|
||||||
const ocrFileName = decodeURIComponent(`${asset.name}${constants.ocrFileExtension}`);
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.storageProvider.deleteFile(labelFileName, true, true),
|
this.storageProvider.deleteFile(labelFileName, true, true),
|
||||||
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
this.storageProvider.writeText(ocrFileName, JSON.stringify(ocrData, null, 2))
|
||||||
]);
|
]);
|
||||||
|
} catch (err) {
|
||||||
|
// The label file may not exist - that's OK.
|
||||||
}
|
}
|
||||||
catch{
|
return null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -175,26 +196,42 @@ export class AssetService {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const extensionParts = fileNameParts[fileNameParts.length - 1].split(/[\?#]/);
|
const extensionParts = fileNameParts[fileNameParts.length - 1].split(/[\?#]/);
|
||||||
let assetFormat = extensionParts[0].toLowerCase();
|
let assetFormat = extensionParts[0].toLowerCase();
|
||||||
|
let assetMimeType = mime.getType(assetFormat);
|
||||||
if (supportedImageFormats.hasOwnProperty(assetFormat)) {
|
if (supportedImageFormats.hasOwnProperty(assetFormat)) {
|
||||||
let types;
|
let checkFileType;
|
||||||
let corruptFileName;
|
let corruptFileName;
|
||||||
if (nodejsMode) {
|
if (nodejsMode) {
|
||||||
const FileType = require('file-type');
|
try {
|
||||||
const fileType = await FileType.fromFile(normalizedPath);
|
checkFileType = await FileType.fromFile(normalizedPath);
|
||||||
types = [fileType.ext];
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
corruptFileName = fileName.split(/[\\\/]/).pop().replace(/%20/g, " ");
|
corruptFileName = fileName.split(/[\\\/]/).pop().replace(/%20/g, " ");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
types = await this.getMimeType(filePath);
|
try {
|
||||||
|
const getFetchSteam = (): Promise<Response> => this.pollForFetchAPI(() => fetch(filePath), 1000, 200);
|
||||||
|
const response = await getFetchSteam();
|
||||||
|
checkFileType = await BrowserFileType.fromStream(response.body);
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
corruptFileName = fileName.split("%2F").pop().replace(/%20/g, " ");
|
corruptFileName = fileName.split("%2F").pop().replace(/%20/g, " ");
|
||||||
}
|
}
|
||||||
if (!types) {
|
let fileType;
|
||||||
|
let mimeType;
|
||||||
|
if (checkFileType) {
|
||||||
|
fileType = checkFileType.ext;
|
||||||
|
mimeType = checkFileType.mime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileType) {
|
||||||
console.error(interpolate(strings.editorPage.assetWarning.incorrectFileExtension.failedToFetch, { fileName: corruptFileName.toLocaleUpperCase() }));
|
console.error(interpolate(strings.editorPage.assetWarning.incorrectFileExtension.failedToFetch, { fileName: corruptFileName.toLocaleUpperCase() }));
|
||||||
}
|
}
|
||||||
// If file was renamed/spoofed - fix file extension to true MIME type and show message
|
// If file was renamed/spoofed - fix file extension to true MIME if it's type is in supported file types and show message
|
||||||
else if (!types.includes(assetFormat)) {
|
else if (fileType !== assetFormat) {
|
||||||
assetFormat = types[0];
|
assetFormat = fileType;
|
||||||
|
assetMimeType = mimeType;
|
||||||
console.error(`${strings.editorPage.assetWarning.incorrectFileExtension.attention} ${corruptFileName.toLocaleUpperCase()} ${strings.editorPage.assetWarning.incorrectFileExtension.text} ${corruptFileName.toLocaleUpperCase()}`);
|
console.error(`${strings.editorPage.assetWarning.incorrectFileExtension.attention} ${corruptFileName.toLocaleUpperCase()} ${strings.editorPage.assetWarning.incorrectFileExtension.text} ${corruptFileName.toLocaleUpperCase()}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,6 +246,7 @@ export class AssetService {
|
||||||
name: fileName,
|
name: fileName,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
size: null,
|
size: null,
|
||||||
|
mimeType: assetMimeType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,36 +271,6 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If extension of a file was spoofed, we fetch only first 4 or needed amount of bytes of the file and read MIME type
|
|
||||||
public static async getMimeType(uri: string): Promise<string[]> {
|
|
||||||
const getFirst4bytes = (): Promise<Response> => this.pollForFetchAPI(() => fetch(uri, { headers: { range: `bytes=0-${mimeBytesNeeded}` } }), 1000, 200);
|
|
||||||
let first4bytes: Response;
|
|
||||||
try {
|
|
||||||
first4bytes = await getFirst4bytes()
|
|
||||||
} catch {
|
|
||||||
return new Promise<string[]>((resolve) => {
|
|
||||||
resolve(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const arrayBuffer: ArrayBuffer = await first4bytes.arrayBuffer();
|
|
||||||
const blob: Blob = new Blob([new Uint8Array(arrayBuffer).buffer]);
|
|
||||||
const isMime = (bytes: Uint8Array, mime: IMime): boolean => {
|
|
||||||
return mime.pattern.every((p, i) => !p || bytes[i] === p);
|
|
||||||
};
|
|
||||||
const fileReader: FileReader = new FileReader();
|
|
||||||
|
|
||||||
return new Promise<string[]>((resolve, reject) => {
|
|
||||||
fileReader.onloadend = (e) => {
|
|
||||||
if (!e || !fileReader.result) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const bytes: Uint8Array = new Uint8Array(fileReader.result as ArrayBuffer);
|
|
||||||
const type: string[] = imageMimes.filter((mime) => isMime(bytes, mime))?.[0]?.types;
|
|
||||||
resolve(type || []);
|
|
||||||
};
|
|
||||||
fileReader.readAsArrayBuffer(blob);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private assetProviderInstance: IAssetProvider;
|
private assetProviderInstance: IAssetProvider;
|
||||||
private storageProviderInstance: IStorageProvider;
|
private storageProviderInstance: IStorageProvider;
|
||||||
|
@ -369,7 +377,7 @@ export class AssetService {
|
||||||
// The file may not exist - that's OK.
|
// The file may not exist - that's OK.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return metadata;
|
return JSON.parse(JSON.stringify(metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -383,6 +391,11 @@ export class AssetService {
|
||||||
try {
|
try {
|
||||||
const json = await this.storageProvider.readText(labelFileName, true);
|
const json = await this.storageProvider.readText(labelFileName, true);
|
||||||
const labelData = JSON.parse(json) as ILabelData;
|
const labelData = JSON.parse(json) as ILabelData;
|
||||||
|
|
||||||
|
if (labelData) {
|
||||||
|
labelData.labelingState = labelData.labelingState || AssetLabelingState.ManuallyLabeled;
|
||||||
|
asset.labelingState = labelData.labelingState;
|
||||||
|
}
|
||||||
// if (!labelData.document || !labelData.labels && !labelData.tableLabels) {
|
// if (!labelData.document || !labelData.labels && !labelData.tableLabels) {
|
||||||
// const reason = interpolate(strings.errors.missingRequiredFieldInLabelFile.message, { labelFileName });
|
// const reason = interpolate(strings.errors.missingRequiredFieldInLabelFile.message, { labelFileName });
|
||||||
// toast.error(reason, { autoClose: false });
|
// toast.error(reason, { autoClose: false });
|
||||||
|
@ -427,7 +440,7 @@ export class AssetService {
|
||||||
// }
|
// }
|
||||||
// toast.dismiss();
|
// toast.dismiss();
|
||||||
return {
|
return {
|
||||||
asset: { ...asset },
|
asset: { ...asset, labelingState: labelData.labelingState },
|
||||||
regions: [],
|
regions: [],
|
||||||
version: appInfo.version,
|
version: appInfo.version,
|
||||||
labelData,
|
labelData,
|
||||||
|
|
|
@ -33,6 +33,7 @@ export class OCRService {
|
||||||
public async getRecognizedText(
|
public async getRecognizedText(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
|
mimeType: string,
|
||||||
onStatusChanged?: (ocrStatus: OcrStatus) => void,
|
onStatusChanged?: (ocrStatus: OcrStatus) => void,
|
||||||
rewrite?: boolean
|
rewrite?: boolean
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
@ -47,11 +48,11 @@ export class OCRService {
|
||||||
notifyStatusChanged(OcrStatus.loadingFromAzureBlob);
|
notifyStatusChanged(OcrStatus.loadingFromAzureBlob);
|
||||||
ocrJson = await this.readOcrFile(ocrFileName);
|
ocrJson = await this.readOcrFile(ocrFileName);
|
||||||
if (!this.isValidOcrFormat(ocrJson) || rewrite) {
|
if (!this.isValidOcrFormat(ocrJson) || rewrite) {
|
||||||
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName);
|
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName, mimeType);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notifyStatusChanged(OcrStatus.runningOCR);
|
notifyStatusChanged(OcrStatus.runningOCR);
|
||||||
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName);
|
ocrJson = await this.fetchOcrUriResult(filePath, fileName, ocrFileName, mimeType);
|
||||||
} finally {
|
} finally {
|
||||||
notifyStatusChanged(OcrStatus.done);
|
notifyStatusChanged(OcrStatus.done);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +82,7 @@ export class OCRService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetchOcrUriResult = async (filePath: string, fileName: string, ocrFileName: string) => {
|
private fetchOcrUriResult = async (filePath: string, fileName: string, ocrFileName: string, mimeType: string) => {
|
||||||
try {
|
try {
|
||||||
let body;
|
let body;
|
||||||
let headers;
|
let headers;
|
||||||
|
@ -93,15 +94,13 @@ export class OCRService {
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
body = bodyAndType[0];
|
body = bodyAndType[0];
|
||||||
const fileType = bodyAndType[1].mime;
|
headers = { "Content-Type": mimeType, "cache-control": "no-cache" };
|
||||||
headers = { "Content-Type": fileType, "cache-control": "no-cache" };
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
body = { url: filePath };
|
body = { url: filePath };
|
||||||
headers = { "Content-Type": "application/json" };
|
headers = { "Content-Type": "application/json" };
|
||||||
}
|
}
|
||||||
const response = await ServiceHelper.postWithAutoRetry(
|
const response = await ServiceHelper.postWithAutoRetry(
|
||||||
this.project.apiUriBase + `/formrecognizer/${constants.apiVersion}/layout/analyze`,
|
this.project.apiUriBase + `/formrecognizer/${ (this.project.apiVersion || constants.apiVersion) }/layout/analyze`,
|
||||||
body,
|
body,
|
||||||
{ headers },
|
{ headers },
|
||||||
this.project.apiKey as string,
|
this.project.apiKey as string,
|
||||||
|
|
|
@ -24,7 +24,7 @@ export class PredictService {
|
||||||
}
|
}
|
||||||
const endpointURL = url.resolve(
|
const endpointURL = url.resolve(
|
||||||
this.project.apiUriBase,
|
this.project.apiUriBase,
|
||||||
`${constants.apiModelsPath}/${modelID}/analyze?includeTextDetails=true`,
|
`${interpolate(constants.apiModelsPath, {apiVersion : (constants.apiVersion || constants.appVersion) })}/${modelID}/analyze?includeTextDetails=true`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const headers = { "Content-Type": "application/json", "cache-control": "no-cache" };
|
const headers = { "Content-Type": "application/json", "cache-control": "no-cache" };
|
||||||
|
@ -60,11 +60,10 @@ export class PredictService {
|
||||||
if (response.data.status.toLowerCase() === constants.statusCodeSucceeded) {
|
if (response.data.status.toLowerCase() === constants.statusCodeSucceeded) {
|
||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
// prediction response from API
|
// prediction response from API
|
||||||
console.log("raw data", JSON.parse(response.request.response));
|
|
||||||
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
} else if (response.data.status.toLowerCase() === constants.statusCodeFailed) {
|
||||||
reject(_.get(
|
reject(_.get(
|
||||||
response,
|
response,
|
||||||
"data.analyzeResult.errors[0].errorMessage",
|
"data.analyzeResult.errors[0]",
|
||||||
"Generic error during prediction"));
|
"Generic error during prediction"));
|
||||||
} else if (Number(new Date()) < endTime) {
|
} else if (Number(new Date()) < endTime) {
|
||||||
// If the request isn't succeeded and the timeout hasn't elapsed, go again
|
// If the request isn't succeeded and the timeout hasn't elapsed, go again
|
||||||
|
|
|
@ -152,14 +152,7 @@ export default class ProjectService implements IProjectService {
|
||||||
|
|
||||||
public async isProjectNameAlreadyUsed(project: IProject): Promise<boolean> {
|
public async isProjectNameAlreadyUsed(project: IProject): Promise<boolean> {
|
||||||
const storageProvider = StorageProviderFactory.createFromConnection(project.sourceConnection);
|
const storageProvider = StorageProviderFactory.createFromConnection(project.sourceConnection);
|
||||||
const fileList = await storageProvider.listFiles("", constants.projectFileExtension/*ext*/);
|
return await storageProvider.isFileExists(`${project.name}${constants.projectFileExtension}`);
|
||||||
for (const fileName of fileList) {
|
|
||||||
if (fileName === `${project.name}${constants.projectFileExtension}`) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async isValidProjectConnection(project: IProject): Promise<boolean> {
|
public async isValidProjectConnection(project: IProject): Promise<boolean> {
|
||||||
|
|
|
@ -8438,7 +8438,7 @@ mime@1.6.0:
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||||
|
|
||||||
mime@^2.4.4, mime@^2.4.5:
|
mime@^2.4.4, mime@^2.4.5, mime@^2.4.6:
|
||||||
version "2.4.6"
|
version "2.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
||||||
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
||||||
|
@ -8708,6 +8708,11 @@ node-forge@0.9.0:
|
||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
|
||||||
integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==
|
integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==
|
||||||
|
|
||||||
|
node-forge@^0.10.0:
|
||||||
|
version "0.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
|
||||||
|
integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
|
||||||
|
|
||||||
node-gyp@^3.8.0:
|
node-gyp@^3.8.0:
|
||||||
version "3.8.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
|
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
|
||||||
|
|
Загрузка…
Ссылка в новой задаче