Support upload prediction results (#864)
* Add toggle for switching between file/service. * Implement prediction file picker (#861) * Handle load file flow. * Add validate logic. * handle the predict result file and draw the result * add condition to prevent nofile error (#862) * Fix error while fetching with file URL. * update prediction file reader and disable prediction selector while no file. Co-authored-by: Buddha Wang <shihw@microsoft.com>
This commit is contained in:
Родитель
b65cea1334
Коммит
0f6c260263
|
@ -2,7 +2,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 5px;
|
||||||
.title {
|
.title {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
@ -13,7 +13,6 @@
|
||||||
}
|
}
|
||||||
.sourceDropdown {
|
.sourceDropdown {
|
||||||
width: 95px;
|
width: 95px;
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
.local-file {
|
.local-file {
|
||||||
float: right;
|
float: right;
|
||||||
|
|
|
@ -9,9 +9,10 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
input[name="pagerange"] {
|
input[name="pagerange"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
}
|
}
|
||||||
.page-range-alert {
|
.page-range-alert {
|
||||||
border: 1px solid red;
|
border: 1px solid #ffffff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ms-Checkbox {
|
.ms-Checkbox {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
.prebuilt-setting {
|
.prebuilt-setting {
|
||||||
.apikeyContainer {
|
.apikeyContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 30px;
|
|
||||||
.ms-TooltipHost,
|
.ms-TooltipHost,
|
||||||
.apikey {
|
.apikey {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
.prediction-file-picker {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
.title {
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-space-between {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-dropdown {
|
||||||
|
width: 95px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.local-file {
|
||||||
|
float: right;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,238 @@
|
||||||
|
import { Dropdown, IDropdownOption, PrimaryButton, TextField } from '@fluentui/react';
|
||||||
|
import React from 'react';
|
||||||
|
import HtmlFileReader from '../../../../common/htmlFileReader';
|
||||||
|
import { strings } from '../../../../common/strings';
|
||||||
|
import { getGreenWithWhiteBackgroundTheme, getPrimaryGreenTheme } from '../../../../common/themes';
|
||||||
|
import "./predictionFilePicker.scss";
|
||||||
|
|
||||||
|
interface IPredictionFile {
|
||||||
|
file: File;
|
||||||
|
fileLabel: string;
|
||||||
|
fetchedFileURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPredictionFilePickerProps {
|
||||||
|
disabled: boolean;
|
||||||
|
onFileChange?: (file: IPredictionFile) => void;
|
||||||
|
onError?: (err: { alertTitle: string, alertMessage: string }) => void;
|
||||||
|
onSelectSourceChange?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPredictionFilePickerState {
|
||||||
|
sourceOption: string;
|
||||||
|
inputedLocalFileName: string;
|
||||||
|
inputedFileURL: string;
|
||||||
|
isFetching: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PredictionFilePicker extends React.Component<IPredictionFilePickerProps, IPredictionFilePickerState>{
|
||||||
|
state = {
|
||||||
|
sourceOption: "localFile",
|
||||||
|
inputedLocalFileName: strings.predict.defaultLocalFileInput,
|
||||||
|
inputedFileURL: "",
|
||||||
|
isFetching: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
private filePicker: React.RefObject<HTMLInputElement> = React.createRef();
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const sourceOptions: IDropdownOption[] = [
|
||||||
|
{ key: "localFile", text: strings.documentFilePicker.localFile },
|
||||||
|
{ key: "url", text: strings.documentFilePicker.url },
|
||||||
|
];
|
||||||
|
|
||||||
|
let { disabled } = this.props;
|
||||||
|
disabled = !!disabled;
|
||||||
|
const urlInputDisabled = disabled || this.state.isFetching;
|
||||||
|
const fetchDisabled: boolean =
|
||||||
|
disabled ||
|
||||||
|
this.state.isFetching ||
|
||||||
|
this.state.inputedFileURL.length === 0 ||
|
||||||
|
this.state.inputedFileURL === strings.prebuiltPredict.defaultURLInput;
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="prediction-file-picker">
|
||||||
|
<div className="title mr-2">Prediction:</div>
|
||||||
|
<div className="container-space-between">
|
||||||
|
<Dropdown
|
||||||
|
className="source-dropdown"
|
||||||
|
selectedKey={this.state.sourceOption}
|
||||||
|
options={sourceOptions}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={this.onSelectSourceChange}
|
||||||
|
/>
|
||||||
|
{this.state.sourceOption === "localFile" &&
|
||||||
|
<>
|
||||||
|
<input ref={this.filePicker}
|
||||||
|
aria-hidden="true"
|
||||||
|
type="file"
|
||||||
|
accept="application/json"
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={this.handleInputFileChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
className="ml-2 local-file"
|
||||||
|
theme={getGreenWithWhiteBackgroundTheme()}
|
||||||
|
style={{ cursor: (disabled ? "default" : "pointer") }}
|
||||||
|
onClick={this.handleInputFileClick}
|
||||||
|
readOnly={true}
|
||||||
|
aria-label={strings.prebuiltPredict.defaultLocalFileInput}
|
||||||
|
value={this.state.inputedLocalFileName}
|
||||||
|
placeholder={strings.predict.defaultLocalFileInput}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{this.state.sourceOption === "url" &&
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
className="mr-2 ml-2"
|
||||||
|
theme={getGreenWithWhiteBackgroundTheme()}
|
||||||
|
onFocus={this.removeDefaultInputedFileURL}
|
||||||
|
onChange={this.setInputedFileURL}
|
||||||
|
aria-label={strings.prebuiltPredict.defaultLocalFileInput}
|
||||||
|
value={this.state.inputedFileURL}
|
||||||
|
disabled={urlInputDisabled}
|
||||||
|
placeholder={strings.predict.defaultURLInput}
|
||||||
|
/>
|
||||||
|
<PrimaryButton
|
||||||
|
theme={getPrimaryGreenTheme()}
|
||||||
|
className="keep-button-80px"
|
||||||
|
text="Fetch"
|
||||||
|
allowDisabledFocus
|
||||||
|
disabled={fetchDisabled}
|
||||||
|
autoFocus={true}
|
||||||
|
onClick={this.getFileFromURL}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSelectSourceChange = (event, option) => {
|
||||||
|
if (option.key !== this.state.sourceOption) {
|
||||||
|
this.setState({
|
||||||
|
sourceOption: option.key,
|
||||||
|
inputedFileURL: ""
|
||||||
|
}, () => {
|
||||||
|
if (this.props.onSelectSourceChange) {
|
||||||
|
this.props.onSelectSourceChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputFileChange = async () => {
|
||||||
|
const { current } = this.filePicker;
|
||||||
|
if (!current.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = current.value.split("\\").pop();
|
||||||
|
if (!fileName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fileInfo = await HtmlFileReader.readAsText(current.files[0]);
|
||||||
|
if (!this.isValidSchema(JSON.parse(fileInfo.content as string))) {
|
||||||
|
// Throw error when invalid schema.
|
||||||
|
throw new Error("The file is not a proper prediction result, please try other file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
inputedLocalFileName: fileName
|
||||||
|
}, () => {
|
||||||
|
if (this.props.onFileChange) {
|
||||||
|
this.props.onFileChange({
|
||||||
|
file: current.files[0],
|
||||||
|
fileLabel: fileName,
|
||||||
|
fetchedFileURL: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// Report error.
|
||||||
|
if (this.props.onError) {
|
||||||
|
this.props.onError({
|
||||||
|
alertTitle: "Load prediction file error",
|
||||||
|
alertMessage: err?.message ? err.message : err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset file input.
|
||||||
|
this.setState({ inputedLocalFileName: "" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputFileClick = () => {
|
||||||
|
this.filePicker.current?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeDefaultInputedFileURL = () => {
|
||||||
|
if (this.state.inputedFileURL === strings.prebuiltPredict.defaultURLInput) {
|
||||||
|
this.setState({ inputedFileURL: "" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setInputedFileURL = (event) => {
|
||||||
|
this.setState({ inputedFileURL: event.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFileFromURL = async () => {
|
||||||
|
this.setState({ isFetching: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.state.inputedFileURL, { headers: { Accept: "application/json" } });
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.status.toString() + " " + response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get("Content-Type");
|
||||||
|
if (!["application/json", "application/octet-stream"].includes(contentType)) {
|
||||||
|
throw new Error("Content-Type " + contentType + " not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
if (!this.isValidSchema(JSON.parse(await blob.text()))) {
|
||||||
|
throw new Error("The file is not a proper prediction result, please try other file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileAsURL = new URL(this.state.inputedFileURL);
|
||||||
|
const fileName = fileAsURL.pathname.split("/").pop();
|
||||||
|
const file = new File([blob], fileName, { type: contentType });
|
||||||
|
this.setState({
|
||||||
|
isFetching: false,
|
||||||
|
}, () => {
|
||||||
|
if (this.props.onFileChange) {
|
||||||
|
this.props.onFileChange({
|
||||||
|
file,
|
||||||
|
fileLabel: fileName,
|
||||||
|
fetchedFileURL: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ isFetching: false });
|
||||||
|
if (this.props.onError) {
|
||||||
|
this.props.onError({
|
||||||
|
alertTitle: "Fetch failed",
|
||||||
|
alertMessage: err?.message ? err.message : err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isValidSchema = (jsonData) => {
|
||||||
|
if (jsonData && jsonData.analyzeResult) {
|
||||||
|
// We should ensure version and documentResults exists.
|
||||||
|
const { version, documentResults } = jsonData.analyzeResult;
|
||||||
|
return !!version && !!documentResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,4 +19,10 @@
|
||||||
textarea {
|
textarea {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.predict-mode-toggle {
|
||||||
|
label {
|
||||||
|
color: rgb(250, 251, 251);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -30,6 +30,7 @@ import ServiceHelper from "../../../../services/serviceHelper";
|
||||||
import { getAppInsights } from "../../../../services/telemetryService";
|
import { getAppInsights } from "../../../../services/telemetryService";
|
||||||
import Alert from "../../common/alert/alert";
|
import Alert from "../../common/alert/alert";
|
||||||
import { DocumentFilePicker } from "../../common/documentFilePicker/documentFilePicker";
|
import { DocumentFilePicker } from "../../common/documentFilePicker/documentFilePicker";
|
||||||
|
import { PredictionFilePicker } from "../../common/predictionFilePicker/predictionFilePicker";
|
||||||
import { ImageMap } from "../../common/imageMap/imageMap";
|
import { ImageMap } from "../../common/imageMap/imageMap";
|
||||||
import { PageRange } from "../../common/pageRange/pageRange";
|
import { PageRange } from "../../common/pageRange/pageRange";
|
||||||
import { PrebuiltSetting } from "../../common/prebuiltSetting/prebuiltSetting";
|
import { PrebuiltSetting } from "../../common/prebuiltSetting/prebuiltSetting";
|
||||||
|
@ -41,6 +42,7 @@ import PredictResult from "../predict/predictResult";
|
||||||
import { ILoadFileHelper, ILoadFileResult, LoadFileHelper } from "./LoadFileHelper";
|
import { ILoadFileHelper, ILoadFileResult, LoadFileHelper } from "./LoadFileHelper";
|
||||||
import "./prebuiltPredictPage.scss";
|
import "./prebuiltPredictPage.scss";
|
||||||
import { ITableHelper, ITableState, TableHelper } from "./tableHelper";
|
import { ITableHelper, ITableState, TableHelper } from "./tableHelper";
|
||||||
|
import { Toggle } from "office-ui-fabric-react/lib/Toggle";
|
||||||
import { ILayoutHelper, LayoutHelper } from "./layoutHelper";
|
import { ILayoutHelper, LayoutHelper } from "./layoutHelper";
|
||||||
|
|
||||||
interface IPrebuiltTypes {
|
interface IPrebuiltTypes {
|
||||||
|
@ -77,6 +79,8 @@ export interface IPrebuiltPredictPageState extends ILoadFileResult, ITableState
|
||||||
pageRange: string;
|
pageRange: string;
|
||||||
pageRangeIsValid?: boolean;
|
pageRangeIsValid?: boolean;
|
||||||
predictionEndpointUrl: string;
|
predictionEndpointUrl: string;
|
||||||
|
|
||||||
|
liveMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: IApplicationState) {
|
function mapStateToProps(state: IApplicationState) {
|
||||||
|
@ -150,6 +154,8 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
withPageRange: false,
|
withPageRange: false,
|
||||||
pageRange: "",
|
pageRange: "",
|
||||||
predictionEndpointUrl: "",
|
predictionEndpointUrl: "",
|
||||||
|
|
||||||
|
liveMode: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
private analyzeResults: any;
|
private analyzeResults: any;
|
||||||
|
@ -248,35 +254,9 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
<FontIcon className="mr-1" iconName="ContactCard" />
|
<FontIcon className="mr-1" iconName="ContactCard" />
|
||||||
<span>{interpolate(strings.prebuiltPredict.anlayWithPrebuiltModels, this.state.currentPrebuiltType)}</span>
|
<span>{interpolate(strings.prebuiltPredict.anlayWithPrebuiltModels, this.state.currentPrebuiltType)}</span>
|
||||||
</h6>
|
</h6>
|
||||||
<div className="p-3 prebuilt-setting" style={{ marginTop: "8px" }}>
|
<Separator className="separator-right-pane-main">1. Choose file for analysis.</Separator>
|
||||||
<h5>{strings.prebuiltSetting.serviceConfigurationTitle}</h5>
|
<div className="p-3">
|
||||||
</div>
|
{/* <h5>{strings.prebuiltPredict.selectFileAndRunAnalysis}</h5> */}
|
||||||
<PrebuiltSetting prebuiltSettings={this.props.prebuiltSettings}
|
|
||||||
disabled={this.state.isPredicting}
|
|
||||||
actions={this.props.actions}
|
|
||||||
/>
|
|
||||||
<div className="p-3" style={{ marginTop: "-3rem" }}>
|
|
||||||
<div className="formtype-section">
|
|
||||||
<div style={{ marginBottom: "3px" }}>{strings.prebuiltPredict.formTypeTitle}</div>
|
|
||||||
<Dropdown
|
|
||||||
disabled={this.state.isPredicting}
|
|
||||||
className="prebuilt-type-dropdown"
|
|
||||||
options={this.prebuiltTypes.map(type => ({ key: type.name, text: type.name }))}
|
|
||||||
defaultSelectedKey={this.state.currentPrebuiltType.name}
|
|
||||||
onChange={this.onPrebuiltTypeChange}></Dropdown>
|
|
||||||
</div>
|
|
||||||
<div className="locales-section" style={{ display: this.state.currentPrebuiltType.useLocale ? "block" : "none" }}>
|
|
||||||
<div style={{ marginBottom: "3px" }}>{strings.prebuiltPredict.locale}</div>
|
|
||||||
<Dropdown
|
|
||||||
disabled={this.state.isPredicting}
|
|
||||||
className="prebuilt-type-dropdown"
|
|
||||||
options={this.locales.map(type => ({ key: type, text: type }))}
|
|
||||||
defaultSelectedKey={this.state.currentLocale}
|
|
||||||
onChange={this.onLocaleChange}></Dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="p-3" style={{ marginTop: "8px" }}>
|
|
||||||
<h5>{strings.prebuiltPredict.selectFileAndRunAnalysis}</h5>
|
|
||||||
<DocumentFilePicker
|
<DocumentFilePicker
|
||||||
disabled={this.state.isPredicting || this.state.isFetching}
|
disabled={this.state.isPredicting || this.state.isFetching}
|
||||||
onFileChange={(data) => this.onFileChange(data)}
|
onFileChange={(data) => this.onFileChange(data)}
|
||||||
|
@ -290,70 +270,110 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
onPageRangeChange={this.onPageRangeChange} />
|
onPageRangeChange={this.onPageRangeChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="separator-right-pane-main">{strings.prebuiltPredict.analysis}</Separator>
|
<Separator className="separator-right-pane-main">2. Get prediction.</Separator>
|
||||||
<div className="p-3" style={{ marginTop: "8px" }}>
|
<div className="p-3" style={{ marginTop: "8px" }}>
|
||||||
<div style={{ marginBottom: "3px" }}>{"The composed API request is"}</div>
|
<Toggle theme={getLightGreyTheme()}
|
||||||
<TextField
|
className="predict-mode-toggle"
|
||||||
className="mb-1 request-uri-textfield"
|
defaultChecked
|
||||||
name="endpointUrl"
|
onText="Call live service"
|
||||||
theme={getLightGreyTheme()}
|
offText="Use predicted file"
|
||||||
value={this.state.predictionEndpointUrl}
|
onChange={this.handleLiveModeToggleChange} />
|
||||||
onChange={this.setRequestURI}
|
</div>
|
||||||
disabled={this.state.isPredicting}
|
{!this.state.liveMode &&
|
||||||
multiline={true}
|
<div className="p-3" style={{ marginTop: "-2rem" }}>
|
||||||
autoAdjustHeight={true}
|
<PredictionFilePicker
|
||||||
/>
|
disabled={this.state.isPredicting || this.state.isFetching || !this.state.file}
|
||||||
<div className="container-items-end predict-button">
|
onFileChange={this.onPredictionFileChange}
|
||||||
<PrimaryButton
|
onSelectSourceChange={this.onPredictionSelectSourceChange}
|
||||||
theme={getPrimaryWhiteTheme()}
|
onError={this.onFileLoadError} />
|
||||||
iconProps={{ iconName: "ContactCard" }}
|
</div>
|
||||||
text={strings.prebuiltPredict.runAnalysis}
|
}
|
||||||
aria-label={!this.state.isPredicting ? strings.prebuiltPredict.inProgress : ""}
|
{this.state.liveMode &&
|
||||||
allowDisabledFocus
|
<>
|
||||||
disabled={predictDisabled}
|
<PrebuiltSetting prebuiltSettings={this.props.prebuiltSettings}
|
||||||
onClick={this.handleClick}
|
disabled={this.state.isPredicting}
|
||||||
|
actions={this.props.actions}
|
||||||
|
/>
|
||||||
|
<div className="p-3" style={{ marginTop: "-28px" }}>
|
||||||
|
<div className="formtype-section">
|
||||||
|
<div style={{ marginBottom: "3px" }}>{strings.prebuiltPredict.formTypeTitle}</div>
|
||||||
|
<Dropdown
|
||||||
|
disabled={this.state.isPredicting}
|
||||||
|
className="prebuilt-type-dropdown"
|
||||||
|
options={this.prebuiltTypes.map(type => ({ key: type.name, text: type.name }))}
|
||||||
|
defaultSelectedKey={this.state.currentPrebuiltType.name}
|
||||||
|
onChange={this.onPrebuiltTypeChange}></Dropdown>
|
||||||
|
</div>
|
||||||
|
<div className="locales-section" style={{ visibility: this.state.currentPrebuiltType.useLocale ? "visible" : "hidden" }}>
|
||||||
|
<div style={{ marginBottom: "3px" }}>{strings.prebuiltPredict.locale}</div>
|
||||||
|
<Dropdown
|
||||||
|
disabled={this.state.isPredicting}
|
||||||
|
className="prebuilt-type-dropdown"
|
||||||
|
options={this.locales.map(type => ({ key: type, text: type }))}
|
||||||
|
defaultSelectedKey={this.state.currentLocale}
|
||||||
|
onChange={this.onLocaleChange}></Dropdown>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: "3px" }}>{"The composed API request is"}</div>
|
||||||
|
<TextField
|
||||||
|
className="mb-1 request-uri-textfield"
|
||||||
|
name="endpointUrl"
|
||||||
|
theme={getLightGreyTheme()}
|
||||||
|
value={this.state.predictionEndpointUrl}
|
||||||
|
onChange={this.setRequestURI}
|
||||||
|
disabled={this.state.isPredicting}
|
||||||
|
multiline={true}
|
||||||
|
autoAdjustHeight={true}
|
||||||
|
/>
|
||||||
|
<div className="container-items-end predict-button">
|
||||||
|
<PrimaryButton
|
||||||
|
theme={getPrimaryWhiteTheme()}
|
||||||
|
iconProps={{ iconName: "ContactCard" }}
|
||||||
|
text={strings.prebuiltPredict.runAnalysis}
|
||||||
|
aria-label={!this.state.isPredicting ? strings.prebuiltPredict.inProgress : ""}
|
||||||
|
allowDisabledFocus
|
||||||
|
disabled={predictDisabled}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</> }
|
||||||
|
{this.state.isFetching &&
|
||||||
|
<div className="loading-container">
|
||||||
|
<Spinner
|
||||||
|
label="Fetching..."
|
||||||
|
ariaLive="assertive"
|
||||||
|
labelPosition="right"
|
||||||
|
size={SpinnerSize.large}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.state.isFetching &&
|
}
|
||||||
<div className="loading-container">
|
{this.state.isPredicting &&
|
||||||
<Spinner
|
<div className="loading-container">
|
||||||
label="Fetching..."
|
<Spinner
|
||||||
ariaLive="assertive"
|
label={strings.prebuiltPredict.inProgress}
|
||||||
labelPosition="right"
|
ariaLive="assertive"
|
||||||
size={SpinnerSize.large}
|
labelPosition="right"
|
||||||
/>
|
size={SpinnerSize.large}
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{this.state.isPredicting &&
|
|
||||||
<div className="loading-container">
|
|
||||||
<Spinner
|
|
||||||
label={strings.prebuiltPredict.inProgress}
|
|
||||||
ariaLive="assertive"
|
|
||||||
labelPosition="right"
|
|
||||||
size={SpinnerSize.large}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{Object.keys(predictions).length > 0 &&
|
|
||||||
<PredictResult
|
|
||||||
predictions={predictions}
|
|
||||||
analyzeResult={this.analyzeResults}
|
|
||||||
page={this.state.currentPage}
|
|
||||||
tags={this.state.tags}
|
|
||||||
downloadPrefix={this.state.currentPrebuiltType.name}
|
|
||||||
downloadResultLabel={this.state.fileLabel}
|
|
||||||
onPredictionClick={this.onPredictionClick}
|
|
||||||
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
|
||||||
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
|
||||||
/>
|
/>
|
||||||
}
|
</div>
|
||||||
{
|
}
|
||||||
(Object.keys(predictions).length === 0 && this.state.predictionLoaded) &&
|
{Object.keys(predictions).length > 0 &&
|
||||||
<div>{strings.prebuiltPredict.noFieldCanBeExtracted}</div>
|
<PredictResult
|
||||||
}
|
predictions={predictions}
|
||||||
</div>
|
analyzeResult={this.analyzeResults}
|
||||||
|
page={this.state.currentPage}
|
||||||
|
tags={this.state.tags}
|
||||||
|
downloadPrefix={this.state.currentPrebuiltType.name}
|
||||||
|
downloadResultLabel={this.state.fileLabel}
|
||||||
|
onPredictionClick={this.onPredictionClick}
|
||||||
|
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
||||||
|
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
(Object.keys(predictions).length === 0 && this.state.predictionLoaded) &&
|
||||||
|
<div>{strings.prebuiltPredict.noFieldCanBeExtracted}</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Alert
|
<Alert
|
||||||
|
@ -381,7 +401,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectSourceChange(): void {
|
onSelectSourceChange = (): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
file: undefined,
|
file: undefined,
|
||||||
analyzeResult: {},
|
analyzeResult: {},
|
||||||
|
@ -392,18 +412,18 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileLoadError(err: { alertTitle: string; alertMessage: string; }): void {
|
onFileLoadError = (err: { alertTitle: string; alertMessage: string; }): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
...err,
|
...err,
|
||||||
shouldShowAlert: true,
|
shouldShowAlert: true,
|
||||||
isPredicting: false,
|
isPredicting: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onFileChange(data: {
|
onFileChange = (data: {
|
||||||
file: File,
|
file: File,
|
||||||
fileLabel: string,
|
fileLabel: string,
|
||||||
fetchedFileURL: string
|
fetchedFileURL: string
|
||||||
}): void {
|
}): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
analyzeResult: null,
|
analyzeResult: null,
|
||||||
|
@ -418,6 +438,57 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPredictionSelectSourceChange = (): void => {
|
||||||
|
this.setState({
|
||||||
|
analyzeResult: {},
|
||||||
|
predictionLoaded: false,
|
||||||
|
});
|
||||||
|
if (this.imageMap) {
|
||||||
|
this.imageMap.removeAllFeatures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPredictionFileChange = (data: {
|
||||||
|
file: File,
|
||||||
|
fileLabel: string,
|
||||||
|
fetchedFileURL: string
|
||||||
|
}): void => {
|
||||||
|
if (this.imageMap) {
|
||||||
|
this.imageMap.removeAllFeatures();
|
||||||
|
}
|
||||||
|
if (data.file) {
|
||||||
|
const makeHandleFile = () => {
|
||||||
|
const handlePredictionResult = this.handlePredictionResult.bind(this);
|
||||||
|
const setState = this.setState.bind(this);
|
||||||
|
return () => {
|
||||||
|
let { result } = reader;
|
||||||
|
if (result instanceof ArrayBuffer) {
|
||||||
|
const dataView = new DataView(result);
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
result = decoder.decode(dataView)
|
||||||
|
}
|
||||||
|
result = JSON.parse(result)
|
||||||
|
setState({
|
||||||
|
currentPage: 1,
|
||||||
|
analyzeResult: null,
|
||||||
|
predictionLoaded: false,
|
||||||
|
fileLoaded: false,
|
||||||
|
}, () => {
|
||||||
|
handlePredictionResult(result);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = makeHandleFile();
|
||||||
|
reader.readAsText(data.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLiveModeToggleChange = (event, checked: boolean) => {
|
||||||
|
this.setState({ liveMode: checked });
|
||||||
|
}
|
||||||
|
|
||||||
private onPrebuiltTypeChange = (e, option: IDropdownOption) => {
|
private onPrebuiltTypeChange = (e, option: IDropdownOption) => {
|
||||||
const currentPrebuiltType = this.prebuiltTypes.find(type => type.name === option.key);
|
const currentPrebuiltType = this.prebuiltTypes.find(type => type.name === option.key);
|
||||||
if (currentPrebuiltType && this.state.currentPrebuiltType.name !== currentPrebuiltType.name) {
|
if (currentPrebuiltType && this.state.currentPrebuiltType.name !== currentPrebuiltType.name) {
|
||||||
|
@ -612,25 +683,27 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
this.setState({ imageAngle: this.state.imageAngle + degrees });
|
this.setState({ imageAngle: this.state.imageAngle + degrees });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handlePredictionResult = (result) => {
|
||||||
|
this.analyzeResults = _.cloneDeep(result);
|
||||||
|
this.tableHelper.setAnalyzeResult(result?.analyzeResult);
|
||||||
|
const tags = this.getTagsForPredictResults(this.getPredictionsFromAnalyzeResult(result?.analyzeResult));
|
||||||
|
this.setState({
|
||||||
|
tags,
|
||||||
|
analyzeResult: result.analyzeResult,
|
||||||
|
predictionLoaded: true,
|
||||||
|
isPredicting: false,
|
||||||
|
}, () => {
|
||||||
|
this.layoutHelper.setLayoutData(result);
|
||||||
|
this.layoutHelper.drawLayout(this.state.currentPage);
|
||||||
|
this.drawPredictionResult();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private handleClick = () => {
|
private handleClick = () => {
|
||||||
this.setState({ predictionLoaded: false, isPredicting: true });
|
this.setState({ predictionLoaded: false, isPredicting: true });
|
||||||
this.getPrediction()
|
this.getPrediction()
|
||||||
.then((result) => {
|
.then(this.handlePredictionResult)
|
||||||
this.analyzeResults = _.cloneDeep(result);
|
.catch((error) => {
|
||||||
this.tableHelper.setAnalyzeResult(result?.analyzeResult);
|
|
||||||
const tags = this.getTagsForPredictResults(this.getPredictionsFromAnalyzeResult(result?.analyzeResult));
|
|
||||||
this.setState({
|
|
||||||
tags,
|
|
||||||
analyzeResult: result.analyzeResult,
|
|
||||||
predictionLoaded: true,
|
|
||||||
isPredicting: false,
|
|
||||||
}, () => {
|
|
||||||
this.layoutHelper.setLayoutData(result);
|
|
||||||
this.layoutHelper.drawLayout(this.state.currentPage);
|
|
||||||
this.drawPredictionResult();
|
|
||||||
});
|
|
||||||
}).catch((error) => {
|
|
||||||
let alertMessage = "";
|
let alertMessage = "";
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
alertMessage = error.response.data;
|
alertMessage = error.response.data;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче