Support table line item in the prebuilt page. (#889)
* Extract regional table. * Support tootip in tableView component. * Support table item in prebuilt page. * update comment. * Fix lint error. * Fix lint error. * Remove console.log and tune style a bit. Co-authored-by: Buddha Wang <scwang0103@gmail.com>
This commit is contained in:
Родитель
60dcc3b57f
Коммит
19e00bff6d
|
@ -0,0 +1,176 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface IRegionalTableState { }
|
||||||
|
|
||||||
|
export interface IRegionalTableProps {
|
||||||
|
regionalTableToView?: any;
|
||||||
|
tableTagColor: string;
|
||||||
|
onMouseEnter: (rowName: string, columnName: string) => void;
|
||||||
|
onMouseLeave: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class RegionalTable extends React.Component<IRegionalTableProps, IRegionalTableState> {
|
||||||
|
makeOnMouseEnter = (rowName, columnName) => () => {
|
||||||
|
this.props.onMouseEnter(rowName, columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.props.onMouseLeave();
|
||||||
|
}
|
||||||
|
|
||||||
|
private displayRegionalTable = (regionalTableToView) => {
|
||||||
|
const tableBody = [];
|
||||||
|
if (regionalTableToView?.type === "array") {
|
||||||
|
const columnHeaderRow = [];
|
||||||
|
const colKeys = Object.keys(regionalTableToView?.valueArray?.[0]?.valueObject || {});
|
||||||
|
if (colKeys.length === 0) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
||||||
|
<span style={{ borderBottom: `4px solid ${this.props.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
|
||||||
|
</h5>
|
||||||
|
<div className="table-view-container">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
Empty table
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < colKeys.length + 1; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
columnHeaderRow.push(
|
||||||
|
<th key={i} className={"empty_header hidden"} />
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
columnHeaderRow.push(
|
||||||
|
<th key={i} className={"column_header"}>
|
||||||
|
{colKeys[i - 1]}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
|
||||||
|
regionalTableToView?.valueArray?.forEach((row, rowIndex) => {
|
||||||
|
const rowName = `#${rowIndex}`;
|
||||||
|
const tableRow = [];
|
||||||
|
tableRow.push(
|
||||||
|
<th key={0} className={"row_header hidden"}>
|
||||||
|
{rowName}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
Object.keys(row?.valueObject).forEach((columnName, columnIndex) => {
|
||||||
|
const tableCell = row?.valueObject?.[columnName];
|
||||||
|
tableRow.push(
|
||||||
|
<td
|
||||||
|
className={"table-cell"}
|
||||||
|
key={columnIndex + 1}
|
||||||
|
onMouseEnter={this.makeOnMouseEnter(rowName, columnName)}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
>
|
||||||
|
{tableCell ? tableCell.text : null}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
tableBody.push(<tr key={(rowIndex + 1)}>{tableRow}</tr>);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const columnHeaderRow = [];
|
||||||
|
const colKeys = Object.keys(regionalTableToView?.valueObject?.[Object.keys(regionalTableToView?.valueObject)?.[0]]?.valueObject || {});
|
||||||
|
if (colKeys.length === 0) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
||||||
|
<span style={{ borderBottom: `4px solid ${this.props.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
|
||||||
|
</h5>
|
||||||
|
<div className="table-view-container">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
Empty table
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < colKeys.length + 1; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
columnHeaderRow.push(
|
||||||
|
<th key={i} className={"empty_header hidden"} />
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
columnHeaderRow.push(
|
||||||
|
<th key={i} className={"column_header"}>
|
||||||
|
{colKeys[i - 1]}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
|
||||||
|
Object.keys(regionalTableToView?.valueObject).forEach((rowName, index) => {
|
||||||
|
const tableRow = [];
|
||||||
|
tableRow.push(
|
||||||
|
<th key={0} className={"row_header"}>
|
||||||
|
{rowName}
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
if (regionalTableToView?.valueObject?.[rowName]) {
|
||||||
|
Object.keys(regionalTableToView?.valueObject?.[rowName]?.valueObject)?.forEach((columnName, index) => {
|
||||||
|
const tableCell = regionalTableToView?.valueObject?.[rowName]?.valueObject?.[columnName];
|
||||||
|
tableRow.push(
|
||||||
|
<td
|
||||||
|
className={"table-cell"}
|
||||||
|
key={index + 1}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
this.setState({ highlightedTableCellRowKey: rowName, highlightedTableCellColumnKey: columnName })
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tableCell ? tableCell.text : null}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
colKeys.forEach((columnName, index) => {
|
||||||
|
tableRow.push(
|
||||||
|
<td
|
||||||
|
className={"table-cell"}
|
||||||
|
key={index + 1}
|
||||||
|
>
|
||||||
|
{null}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tableBody.push(<tr key={index + 1}>{tableRow}</tr>);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
||||||
|
<span style={{ borderBottom: `4px solid ${this.props.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
|
||||||
|
</h5>
|
||||||
|
<div className="table-view-container">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
{tableBody}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.displayRegionalTable(this.props.regionalTableToView)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,13 +2,40 @@ import * as React from "react";
|
||||||
import { ICustomizations, Customizer, ContextualMenu, IDragOptions, Modal, FontIcon } from "@fluentui/react";
|
import { ICustomizations, Customizer, ContextualMenu, IDragOptions, Modal, FontIcon } from "@fluentui/react";
|
||||||
import { getDarkGreyTheme } from "../../../../common/themes";
|
import { getDarkGreyTheme } from "../../../../common/themes";
|
||||||
import "./tableView.scss";
|
import "./tableView.scss";
|
||||||
|
import { TooltipHost, TooltipDelay, DirectionalHint, ITooltipProps, ITooltipHostStyles } from "@fluentui/react";
|
||||||
|
import { useId } from '@uifabric/react-hooks';
|
||||||
|
|
||||||
|
function Tooltip({ children, content }) {
|
||||||
|
const makeTooltipProps = (content: object): ITooltipProps => ({
|
||||||
|
onRenderContent: () => (
|
||||||
|
<ul style={{ margin: 10, padding: 0 }}>
|
||||||
|
{Object.keys(content).map((key, index) => content[key] ? <li key={index}>{`${key}: ${content[key]}`}</li> : null)}
|
||||||
|
</ul>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const hostStyles: Partial<ITooltipHostStyles> = { root: { display: 'inline-block' } };
|
||||||
|
const tooltipId = useId('tooltip');
|
||||||
|
const tooltipProps = makeTooltipProps(content);
|
||||||
|
return (
|
||||||
|
<TooltipHost
|
||||||
|
delay={TooltipDelay.zero}
|
||||||
|
directionalHint={DirectionalHint.topCenter}
|
||||||
|
id={tooltipId}
|
||||||
|
tooltipProps={tooltipProps}
|
||||||
|
styles={hostStyles}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TooltipHost>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface ITableViewProps {
|
interface ITableViewProps {
|
||||||
handleTableViewClose: () => any;
|
handleTableViewClose: () => any;
|
||||||
tableToView: object;
|
tableToView: object;
|
||||||
|
showToolTips?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TableView: React.FunctionComponent<ITableViewProps> = (props) => {
|
export const TableView: React.FunctionComponent<ITableViewProps> = ({ handleTableViewClose, tableToView, showToolTips = false }) => {
|
||||||
const dark: ICustomizations = {
|
const dark: ICustomizations = {
|
||||||
settings: {
|
settings: {
|
||||||
theme: getDarkGreyTheme(),
|
theme: getDarkGreyTheme(),
|
||||||
|
@ -20,49 +47,52 @@ export const TableView: React.FunctionComponent<ITableViewProps> = (props) => {
|
||||||
moveMenuItemText: "Move",
|
moveMenuItemText: "Move",
|
||||||
closeMenuItemText: "Close",
|
closeMenuItemText: "Close",
|
||||||
menu: ContextualMenu,
|
menu: ContextualMenu,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getTableBody() {
|
function getTableBody() {
|
||||||
const table = props.tableToView;
|
const table = tableToView;
|
||||||
let tableBody = null;
|
let tableBody = null;
|
||||||
if (table !== null) {
|
if (table !== null) {
|
||||||
tableBody = [];
|
tableBody = [];
|
||||||
const rows = table["rows"];
|
const rows = table["rows"];
|
||||||
// const columns = table["columns"];
|
|
||||||
for (let i = 0; i < rows; i++) {
|
for (let i = 0; i < rows; i++) {
|
||||||
const tableRow = [];
|
const tableRow = [];
|
||||||
tableBody.push(<tr key={i}>{tableRow}</tr>);
|
tableBody.push(<tr key={i}>{tableRow}</tr>);
|
||||||
}
|
}
|
||||||
table["cells"].forEach((cell) => {
|
table["cells"].forEach(({ rowIndex, columnIndex, rowSpan, colSpan, text, confidence }) => {
|
||||||
const rowIndex = cell["rowIndex"];
|
const content = { confidence: confidence || null };
|
||||||
const columnIndex = cell["columnIndex"];
|
const hasContentValue = Object.values(content).reduce((hasValue, value) => value || hasValue, false);
|
||||||
const rowSpan = cell["rowSpan"];
|
tableBody[rowIndex]["props"]["children"][columnIndex] = (
|
||||||
const colSpan = cell["columnSpan"];
|
|
||||||
tableBody[rowIndex]["props"]["children"][columnIndex] =
|
|
||||||
<td key={columnIndex} colSpan={colSpan} rowSpan={rowSpan}>
|
<td key={columnIndex} colSpan={colSpan} rowSpan={rowSpan}>
|
||||||
{cell["text"]}
|
{showToolTips && hasContentValue ? (
|
||||||
</td>;
|
<Tooltip content={content}>
|
||||||
|
{text}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<React.Fragment>{text}</React.Fragment>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return tableBody;
|
return tableBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Customizer {...dark}>
|
<Customizer {...dark}>
|
||||||
<Modal
|
<Modal
|
||||||
titleAriaId={"Table view"}
|
titleAriaId={"Table view"}
|
||||||
isOpen={props.tableToView !== null}
|
isOpen={tableToView !== null}
|
||||||
isModeless={false}
|
isModeless={false}
|
||||||
isDarkOverlay={true}
|
isDarkOverlay={true}
|
||||||
dragOptions={dragOptions}
|
dragOptions={dragOptions}
|
||||||
onDismiss={props.handleTableViewClose}
|
onDismiss={handleTableViewClose}
|
||||||
scrollableContentClassName={"table-view-scrollable-content"}
|
scrollableContentClassName={"table-view-scrollable-content"}
|
||||||
>
|
>
|
||||||
<FontIcon
|
<FontIcon
|
||||||
className="close-modal"
|
className="close-modal"
|
||||||
role="button"
|
role="button"
|
||||||
iconName="Cancel"
|
iconName="Cancel"
|
||||||
onClick={props.handleTableViewClose}
|
onClick={handleTableViewClose}
|
||||||
/>
|
/>
|
||||||
<div className="table-view-container">
|
<div className="table-view-container">
|
||||||
<table className="viewed-table">
|
<table className="viewed-table">
|
||||||
|
|
|
@ -38,7 +38,7 @@ import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
||||||
import { CanvasCommandBar } from "../editorPage/canvasCommandBar";
|
import { CanvasCommandBar } from "../editorPage/canvasCommandBar";
|
||||||
import { TableView } from "../editorPage/tableView";
|
import { TableView } from "../editorPage/tableView";
|
||||||
import "../predict/predictPage.scss";
|
import "../predict/predictPage.scss";
|
||||||
import PredictResult from "../predict/predictResult";
|
import PredictResult, { ITableResultItem } 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";
|
||||||
|
@ -83,6 +83,10 @@ export interface IPrebuiltPredictPageState extends ILoadFileResult, ITableState
|
||||||
predictionEndpointUrl: string;
|
predictionEndpointUrl: string;
|
||||||
|
|
||||||
liveMode: boolean;
|
liveMode: boolean;
|
||||||
|
|
||||||
|
viewRegionalTable?: boolean;
|
||||||
|
regionalTableToView?: any;
|
||||||
|
tableTagColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state: IApplicationState) {
|
function mapStateToProps(state: IApplicationState) {
|
||||||
|
@ -158,6 +162,10 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
predictionEndpointUrl: "",
|
predictionEndpointUrl: "",
|
||||||
|
|
||||||
liveMode: true,
|
liveMode: true,
|
||||||
|
|
||||||
|
viewRegionalTable: false,
|
||||||
|
regionalTableToView: null,
|
||||||
|
tableTagColor: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
private analyzeResults: any;
|
private analyzeResults: any;
|
||||||
|
@ -203,7 +211,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
if (prevState.highlightedField !== this.state.highlightedField) {
|
if (prevState.highlightedField !== this.state.highlightedField) {
|
||||||
this.setPredictedFieldHighlightStatus(this.state.highlightedField);
|
this.setPredictedFieldHighlightStatus(this.state.highlightedField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_prevProps.prebuiltSettings !== this.props.prebuiltSettings) {
|
if (_prevProps.prebuiltSettings !== this.props.prebuiltSettings) {
|
||||||
this.handleUpdateRequestURI();
|
this.handleUpdateRequestURI();
|
||||||
|
@ -229,7 +237,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
!this.state.fileLoaded ||
|
!this.state.fileLoaded ||
|
||||||
(this.state.withPageRange && !this.state.pageRangeIsValid) ||
|
(this.state.withPageRange && !this.state.pageRangeIsValid) ||
|
||||||
(needEndPoint && (!this.props.prebuiltSettings?.apiKey ||
|
(needEndPoint && (!this.props.prebuiltSettings?.apiKey ||
|
||||||
!this.props.prebuiltSettings?.serviceURI));
|
!this.props.prebuiltSettings?.serviceURI));
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
@ -372,12 +380,20 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
onPredictionClick={this.onPredictionClick}
|
onPredictionClick={this.onPredictionClick}
|
||||||
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
||||||
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
||||||
|
onTablePredictionClick={this.onTablePredictionClick}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(Object.keys(predictions).length === 0 && this.state.predictionLoaded) &&
|
(Object.keys(predictions).length === 0 && this.state.predictionLoaded) &&
|
||||||
<div>{strings.prebuiltPredict.noFieldCanBeExtracted}</div>
|
<div>{strings.prebuiltPredict.noFieldCanBeExtracted}</div>
|
||||||
}
|
}
|
||||||
|
{this.state.viewRegionalTable &&
|
||||||
|
<TableView
|
||||||
|
handleTableViewClose={this.onTablePredictionClose}
|
||||||
|
tableToView={this.state.regionalTableToView}
|
||||||
|
showToolTips={true}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Alert
|
<Alert
|
||||||
|
@ -462,7 +478,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
}
|
}
|
||||||
if (data.file) {
|
if (data.file) {
|
||||||
HtmlFileReader.readAsText(data.file)
|
HtmlFileReader.readAsText(data.file)
|
||||||
.then(({ content }) => JSON.parse(content as string))
|
.then(({ content }) => JSON.parse(content as string))
|
||||||
.then(result => this.setState({
|
.then(result => this.setState({
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
analyzeResult: null,
|
analyzeResult: null,
|
||||||
|
@ -470,7 +486,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
fileLoaded: false,
|
fileLoaded: false,
|
||||||
}, () => new Promise(() => this.handlePredictionResult(result))
|
}, () => new Promise(() => this.handlePredictionResult(result))
|
||||||
.catch(this.handlePredictionError)))
|
.catch(this.handlePredictionError)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLiveModeToggleChange = (event, checked: boolean) => {
|
handleLiveModeToggleChange = (event, checked: boolean) => {
|
||||||
|
@ -689,22 +705,22 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
}
|
}
|
||||||
|
|
||||||
private handlePredictionError = (error) => {
|
private handlePredictionError = (error) => {
|
||||||
let alertMessage = "";
|
let alertMessage = "";
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
alertMessage = error.response.data;
|
alertMessage = error.response.data;
|
||||||
} else if (error.errorCode === ErrorCode.PredictWithoutTrainForbidden) {
|
} else if (error.errorCode === ErrorCode.PredictWithoutTrainForbidden) {
|
||||||
alertMessage = strings.errors.predictWithoutTrainForbidden.message;
|
alertMessage = strings.errors.predictWithoutTrainForbidden.message;
|
||||||
} else if (error.errorCode === ErrorCode.ModelNotFound) {
|
} else if (error.errorCode === ErrorCode.ModelNotFound) {
|
||||||
alertMessage = error.message;
|
alertMessage = error.message;
|
||||||
} else {
|
} else {
|
||||||
alertMessage = interpolate(strings.errors.endpointConnectionError.message, { endpoint: "form recognizer backend URL" });
|
alertMessage = interpolate(strings.errors.endpointConnectionError.message, { endpoint: "form recognizer backend URL" });
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
shouldShowAlert: true,
|
shouldShowAlert: true,
|
||||||
alertTitle: "Prediction Failed",
|
alertTitle: "Prediction Failed",
|
||||||
alertMessage,
|
alertMessage,
|
||||||
isPredicting: false,
|
isPredicting: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleClick = () => {
|
private handleClick = () => {
|
||||||
|
@ -810,72 +826,89 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
private drawPredictionResult = (): void => {
|
private drawPredictionResult = (): void => {
|
||||||
// Comment this line to prevent clear OCR boundary boxes.
|
// Comment this line to prevent clear OCR boundary boxes.
|
||||||
// this.imageMap.removeAllFeatures();
|
// this.imageMap.removeAllFeatures();
|
||||||
|
const createFeature = (fieldName, field) => {
|
||||||
|
if (Array.isArray(field)) {
|
||||||
|
field.forEach(field => createFeature(fieldName, field))
|
||||||
|
} else {
|
||||||
|
if (_.get(field, "page", null) === this.state.currentPage) {
|
||||||
|
const text = fieldName;
|
||||||
|
const boundingbox = _.get(field, "boundingBox", []);
|
||||||
|
const feature = this.createBoundingBoxVectorFeature(text, boundingbox, imageExtent, ocrExtent);
|
||||||
|
features.push(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const features = [];
|
const features = [];
|
||||||
const imageExtent = [0, 0, this.state.imageWidth, this.state.imageHeight];
|
const imageExtent = [0, 0, this.state.imageWidth, this.state.imageHeight];
|
||||||
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(this.state.analyzeResult)[this.state.currentPage - 1];
|
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(this.state.analyzeResult)[this.state.currentPage - 1];
|
||||||
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
||||||
const predictions = this.getPredictionsFromAnalyzeResult(this.state.analyzeResult);
|
const predictions = this.flatFields(this.getPredictionsFromAnalyzeResult(this.state.analyzeResult));
|
||||||
for (const fieldName of Object.keys(predictions)) {
|
for (const [fieldName, field] of Object.entries(predictions)) {
|
||||||
const field = predictions[fieldName];
|
createFeature(fieldName, field);
|
||||||
if (_.get(field, "page", null) === this.state.currentPage) {
|
|
||||||
const text = fieldName;
|
|
||||||
const boundingbox = _.get(field, "boundingBox", []);
|
|
||||||
const feature = this.createBoundingBoxVectorFeature(text, boundingbox, imageExtent, ocrExtent);
|
|
||||||
features.push(feature);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.imageMap.addFeatures(features);
|
this.imageMap.addFeatures(features);
|
||||||
this.tableHelper.drawTables(this.state.currentPage);
|
this.tableHelper.drawTables(this.state.currentPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPredictionsFromAnalyzeResult(analyzeResult: any) {
|
private flatFields = (fields: object = {}): { [key: string]: (object[] | object) } => {
|
||||||
if (analyzeResult) {
|
/**
|
||||||
const documentResults = _.get(analyzeResult, "documentResults", []);
|
* @param fields: primitive types, object types likes array, object, and root level field
|
||||||
const isSupportField = fieldName => {
|
* @return flattenfields, a field props or an array of field props
|
||||||
// Define list of unsupported field names.
|
*/
|
||||||
const blockedFieldNames = ["ReceiptType"];
|
const flattedFields = {};
|
||||||
return blockedFieldNames.indexOf(fieldName) === -1;
|
const isSupportField = fieldName => {
|
||||||
}
|
// Define list of unsupported field names.
|
||||||
const isRootItemObject = obj => obj.hasOwnProperty("text");
|
const blockedFieldNames = ["ReceiptType"];
|
||||||
// flat fieldProps of type "array" and "object", and extract root level field props in "object" type
|
return blockedFieldNames.indexOf(fieldName) === -1;
|
||||||
const flattedFields = {};
|
}
|
||||||
const flatFields = (fields = {}) => {
|
const isRootItemObject = obj => obj.hasOwnProperty("text");
|
||||||
const flatFieldProps = (displayName, fieldProps) => {
|
// flat fieldProps of type "array" and "object", and extract root level field props in "object" type
|
||||||
if (isSupportField(displayName)) {
|
const flatFieldProps = (fieldName, fieldProps) => {
|
||||||
switch (_.get(fieldProps, "type", "")) {
|
if (isSupportField(fieldName)) {
|
||||||
case "array": {
|
switch (_.get(fieldProps, "type", "")) {
|
||||||
const valueArray = _.get(fieldProps, "valueArray", []);
|
case "array": {
|
||||||
for (const [index, valueArrayItem] of valueArray.entries()) {
|
const valueArray = _.get(fieldProps, "valueArray", []);
|
||||||
flatFieldProps(`${displayName} ${index + 1}`, valueArrayItem);
|
for (const arrayItem of valueArray) {
|
||||||
}
|
flatFieldProps(fieldName, arrayItem);
|
||||||
break;
|
}
|
||||||
}
|
break;
|
||||||
case "object": {
|
}
|
||||||
// root level field props
|
case "object": {
|
||||||
const { type, valueObject, ...restProps } = fieldProps;
|
// root level field props
|
||||||
if (isRootItemObject(restProps)) {
|
const { type, valueObject, ...restProps } = fieldProps;
|
||||||
flatFieldProps(displayName, restProps);
|
if (isRootItemObject(restProps)) {
|
||||||
}
|
flatFieldProps(fieldName, restProps);
|
||||||
for (const [fieldName, objFieldProps] of Object.entries(fieldProps.valueObject)) {
|
}
|
||||||
flatFieldProps(`${displayName}: ${fieldName}`, objFieldProps);
|
for (const objFieldProps of Object.values(fieldProps.valueObject)) {
|
||||||
}
|
if (objFieldProps) {
|
||||||
break;
|
flatFieldProps(fieldName, objFieldProps);
|
||||||
}
|
|
||||||
default: {
|
|
||||||
flattedFields[displayName] = fieldProps;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (flattedFields[fieldName] == null) {
|
||||||
|
flattedFields[fieldName] = fieldProps;
|
||||||
|
}
|
||||||
|
else if (Array.isArray(flattedFields[fieldName])) {
|
||||||
|
flattedFields[fieldName].push(fieldProps)
|
||||||
|
} else {
|
||||||
|
flattedFields[fieldName] = [flattedFields[fieldName], fieldProps];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [fieldName, fieldProps] of Object.entries(fields)) {
|
|
||||||
flatFieldProps(fieldName, fieldProps);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (const documentResult of documentResults) {
|
}
|
||||||
const fields = documentResult["fields"];
|
for (const [fieldName, fieldProps] of Object.entries(fields)) {
|
||||||
flatFields(fields);
|
flatFieldProps(fieldName, fieldProps);
|
||||||
}
|
}
|
||||||
return flattedFields;
|
return flattedFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPredictionsFromAnalyzeResult(analyzeResult: any) {
|
||||||
|
if (analyzeResult) {
|
||||||
|
const documentResults = _.get(analyzeResult, "documentResults", []);
|
||||||
|
return documentResults.reduce((accFields, documentResult) => ({ ...accFields, ...documentResult.fields }), {});
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -997,4 +1030,45 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
||||||
predictionEndpointUrl: newValue
|
predictionEndpointUrl: newValue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onTablePredictionClick = (predictedItem: ITableResultItem, tagColor: string) => {
|
||||||
|
const makeTable = (clickedFieldName) => {
|
||||||
|
function Cell(rowIndex, columnIndex, text = null, confidence = null) {
|
||||||
|
this.rowIndex = rowIndex;
|
||||||
|
this.columnIndex = columnIndex;
|
||||||
|
this.text = text;
|
||||||
|
this.confidence = confidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueArray = clickedFieldName.valueArray || [];
|
||||||
|
const columnNames = Object.keys(valueArray[0].valueObject);
|
||||||
|
const columnHeaders = function makeColumnHeaders() {
|
||||||
|
const indexColumn = new Cell(0, 0, "");
|
||||||
|
const contentColumns = columnNames.map((columnName, columnIndex) => new Cell(0, columnIndex + 1, columnName));
|
||||||
|
return [indexColumn, ...contentColumns];
|
||||||
|
}()
|
||||||
|
const matrix: any[] = [columnHeaders];
|
||||||
|
for (let i = 0; i < valueArray.length; i++) {
|
||||||
|
const valueObject = valueArray[i].valueObject || {};
|
||||||
|
const indexColumn = new Cell(i + 1, 0, `#${i + 1}`);
|
||||||
|
const contentColumns = columnNames.map((columnName, columnIndex) => {
|
||||||
|
const { text, confidence } = valueObject[columnName] || {};
|
||||||
|
return new Cell(i + 1, columnIndex + 1, text, confidence);
|
||||||
|
});
|
||||||
|
matrix.push([indexColumn, ...contentColumns]);
|
||||||
|
}
|
||||||
|
const flattenCells = matrix.reduce((cells, row) => cells = [...cells, ...row], []);
|
||||||
|
return { cells: flattenCells, columns: matrix[0].length, rows: matrix.length, fieldName: clickedField, tagColor };
|
||||||
|
}
|
||||||
|
|
||||||
|
const predictions = this.getPredictionsFromAnalyzeResult(this.state.analyzeResult)
|
||||||
|
const clickedFieldName = predictedItem?.fieldName;
|
||||||
|
const clickedField = predictions[clickedFieldName];
|
||||||
|
const regionalTableToView = makeTable(clickedField);
|
||||||
|
this.setState({ viewRegionalTable: true, regionalTableToView });
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTablePredictionClose = () => {
|
||||||
|
this.setState({ viewRegionalTable: false, regionalTableToView: null })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ import "./predictPage.scss";
|
||||||
import PredictResult, { IAnalyzeModelInfo, ITableResultItem } from "./predictResult";
|
import PredictResult, { IAnalyzeModelInfo, ITableResultItem } from "./predictResult";
|
||||||
import RecentModelsView from "./recentModelsView";
|
import RecentModelsView from "./recentModelsView";
|
||||||
import { UploadToTrainingSetView } from "./uploadToTrainingSetView";
|
import { UploadToTrainingSetView } from "./uploadToTrainingSetView";
|
||||||
|
import RegionalTable from "../../common/regionalTable/regionalTable";
|
||||||
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
||||||
|
|
||||||
|
@ -441,7 +442,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
{this.state.viewRegionalTable &&
|
{this.state.viewRegionalTable &&
|
||||||
<div className="m-2">
|
<div className="m-2">
|
||||||
<h4 className="ml-1 mb-4">View analyzed Table</h4>
|
<h4 className="ml-1 mb-4">View analyzed Table</h4>
|
||||||
{this.displayRegionalTable(this.state.regionalTableToView)}
|
<RegionalTable
|
||||||
|
regionalTableToView={this.state.regionalTableToView}
|
||||||
|
tableTagColor={this.state.tableTagColor}
|
||||||
|
onMouseEnter={this.onMouseEnter}
|
||||||
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
/>
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
className="mt-4 ml-2"
|
className="mt-4 ml-2"
|
||||||
theme={getPrimaryGreyTheme()}
|
theme={getPrimaryGreyTheme()}
|
||||||
|
@ -1072,158 +1078,6 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
this.setState({ viewRegionalTable: true, regionalTableToView: predictedItem, tableTagColor: tagColor });
|
this.setState({ viewRegionalTable: true, regionalTableToView: predictedItem, tableTagColor: tagColor });
|
||||||
}
|
}
|
||||||
|
|
||||||
private displayRegionalTable = (regionalTableToView) => {
|
|
||||||
|
|
||||||
const tableBody = [];
|
|
||||||
if (regionalTableToView?.type === "array") {
|
|
||||||
const columnHeaderRow = [];
|
|
||||||
const colKeys = Object.keys(regionalTableToView?.valueArray?.[0]?.valueObject || {});
|
|
||||||
if (colKeys.length === 0) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
|
||||||
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
|
|
||||||
</h5>
|
|
||||||
<div className="table-view-container">
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
Empty table
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < colKeys.length + 1; i++) {
|
|
||||||
if (i === 0) {
|
|
||||||
columnHeaderRow.push(
|
|
||||||
<th key={i} className={"empty_header hidden"} />
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
columnHeaderRow.push(
|
|
||||||
<th key={i} className={"column_header"}>
|
|
||||||
{colKeys[i - 1]}
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
|
|
||||||
regionalTableToView?.valueArray?.forEach((row, rowIndex) => {
|
|
||||||
const tableRow = [];
|
|
||||||
tableRow.push(
|
|
||||||
<th key={0} className={"row_header hidden"}>
|
|
||||||
{"#" + rowIndex}
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
Object.keys(row?.valueObject).forEach((columnName, columnIndex) => {
|
|
||||||
const tableCell = row?.valueObject?.[columnName];
|
|
||||||
tableRow.push(
|
|
||||||
<td
|
|
||||||
className={"table-cell"}
|
|
||||||
key={columnIndex + 1}
|
|
||||||
onMouseEnter={() => {
|
|
||||||
this.setState({ highlightedTableCellRowKey: "#" + rowIndex, highlightedTableCellColumnKey: columnName })
|
|
||||||
}}
|
|
||||||
onMouseLeave={() => {
|
|
||||||
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tableCell ? tableCell.text : null}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
tableBody.push(<tr key={(rowIndex + 1)}>{tableRow}</tr>);
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const columnHeaderRow = [];
|
|
||||||
const colKeys = Object.keys(regionalTableToView?.valueObject?.[Object.keys(regionalTableToView?.valueObject)?.[0]]?.valueObject || {});
|
|
||||||
if (colKeys.length === 0) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
|
||||||
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
|
|
||||||
</h5>
|
|
||||||
<div className="table-view-container">
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
Empty table
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < colKeys.length + 1; i++) {
|
|
||||||
if (i === 0) {
|
|
||||||
columnHeaderRow.push(
|
|
||||||
<th key={i} className={"empty_header hidden"} />
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
columnHeaderRow.push(
|
|
||||||
<th key={i} className={"column_header"}>
|
|
||||||
{colKeys[i - 1]}
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tableBody.push(<tr key={0}>{columnHeaderRow}</tr>);
|
|
||||||
Object.keys(regionalTableToView?.valueObject).forEach((rowName, index) => {
|
|
||||||
const tableRow = [];
|
|
||||||
tableRow.push(
|
|
||||||
<th key={0} className={"row_header"}>
|
|
||||||
{rowName}
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
if (regionalTableToView?.valueObject?.[rowName]) {
|
|
||||||
Object.keys(regionalTableToView?.valueObject?.[rowName]?.valueObject)?.forEach((columnName, index) => {
|
|
||||||
const tableCell = regionalTableToView?.valueObject?.[rowName]?.valueObject?.[columnName];
|
|
||||||
tableRow.push(
|
|
||||||
<td
|
|
||||||
className={"table-cell"}
|
|
||||||
key={index + 1}
|
|
||||||
onMouseEnter={() => {
|
|
||||||
this.setState({ highlightedTableCellRowKey: rowName, highlightedTableCellColumnKey: columnName })
|
|
||||||
}}
|
|
||||||
onMouseLeave={() => {
|
|
||||||
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tableCell ? tableCell.text : null}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
colKeys.forEach((columnName, index) => {
|
|
||||||
tableRow.push(
|
|
||||||
<td
|
|
||||||
className={"table-cell"}
|
|
||||||
key={index + 1}
|
|
||||||
>
|
|
||||||
{null}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
tableBody.push(<tr key={index + 1}>{tableRow}</tr>);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h5 className="mb-4 ml-2 mt-2 pb-1">
|
|
||||||
<span style={{ borderBottom: `4px solid ${this.state.tableTagColor}` }}>Table name: {regionalTableToView.fieldName}</span>
|
|
||||||
</h5>
|
|
||||||
<div className="table-view-container">
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
{tableBody}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPredictionMouseEnter = (predictedItem: any) => {
|
private onPredictionMouseEnter = (predictedItem: any) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedField: predictedItem.fieldName ?? "",
|
highlightedField: predictedItem.fieldName ?? "",
|
||||||
|
@ -1376,4 +1230,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
||||||
this.props.appTitleActions.setTitle(project.name);
|
this.props.appTitleActions.setTitle(project.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onMouseEnter = (rowName: string, columnName: string) => {
|
||||||
|
this.setState({ highlightedTableCellRowKey: rowName, highlightedTableCellColumnKey: columnName });
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMouseLeave = () => {
|
||||||
|
this.setState({ highlightedTableCellRowKey: null, highlightedTableCellColumnKey: null });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче