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 { getDarkGreyTheme } from "../../../../common/themes";
|
||||
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 {
|
||||
handleTableViewClose: () => any;
|
||||
tableToView: object;
|
||||
showToolTips?: boolean;
|
||||
}
|
||||
|
||||
export const TableView: React.FunctionComponent<ITableViewProps> = (props) => {
|
||||
export const TableView: React.FunctionComponent<ITableViewProps> = ({ handleTableViewClose, tableToView, showToolTips = false }) => {
|
||||
const dark: ICustomizations = {
|
||||
settings: {
|
||||
theme: getDarkGreyTheme(),
|
||||
|
@ -20,49 +47,52 @@ export const TableView: React.FunctionComponent<ITableViewProps> = (props) => {
|
|||
moveMenuItemText: "Move",
|
||||
closeMenuItemText: "Close",
|
||||
menu: ContextualMenu,
|
||||
};
|
||||
};
|
||||
|
||||
function getTableBody() {
|
||||
const table = props.tableToView;
|
||||
const table = tableToView;
|
||||
let tableBody = null;
|
||||
if (table !== null) {
|
||||
tableBody = [];
|
||||
const rows = table["rows"];
|
||||
// const columns = table["columns"];
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const tableRow = [];
|
||||
tableBody.push(<tr key={i}>{tableRow}</tr>);
|
||||
}
|
||||
table["cells"].forEach((cell) => {
|
||||
const rowIndex = cell["rowIndex"];
|
||||
const columnIndex = cell["columnIndex"];
|
||||
const rowSpan = cell["rowSpan"];
|
||||
const colSpan = cell["columnSpan"];
|
||||
tableBody[rowIndex]["props"]["children"][columnIndex] =
|
||||
table["cells"].forEach(({ rowIndex, columnIndex, rowSpan, colSpan, text, confidence }) => {
|
||||
const content = { confidence: confidence || null };
|
||||
const hasContentValue = Object.values(content).reduce((hasValue, value) => value || hasValue, false);
|
||||
tableBody[rowIndex]["props"]["children"][columnIndex] = (
|
||||
<td key={columnIndex} colSpan={colSpan} rowSpan={rowSpan}>
|
||||
{cell["text"]}
|
||||
</td>;
|
||||
{showToolTips && hasContentValue ? (
|
||||
<Tooltip content={content}>
|
||||
{text}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<React.Fragment>{text}</React.Fragment>
|
||||
)}
|
||||
</td>
|
||||
)
|
||||
});
|
||||
}
|
||||
return tableBody;
|
||||
}
|
||||
|
||||
return (
|
||||
<Customizer {...dark}>
|
||||
<Modal
|
||||
titleAriaId={"Table view"}
|
||||
isOpen={props.tableToView !== null}
|
||||
isOpen={tableToView !== null}
|
||||
isModeless={false}
|
||||
isDarkOverlay={true}
|
||||
dragOptions={dragOptions}
|
||||
onDismiss={props.handleTableViewClose}
|
||||
onDismiss={handleTableViewClose}
|
||||
scrollableContentClassName={"table-view-scrollable-content"}
|
||||
>
|
||||
<FontIcon
|
||||
className="close-modal"
|
||||
role="button"
|
||||
iconName="Cancel"
|
||||
onClick={props.handleTableViewClose}
|
||||
<FontIcon
|
||||
className="close-modal"
|
||||
role="button"
|
||||
iconName="Cancel"
|
||||
onClick={handleTableViewClose}
|
||||
/>
|
||||
<div className="table-view-container">
|
||||
<table className="viewed-table">
|
||||
|
|
|
@ -38,7 +38,7 @@ import PreventLeaving from "../../common/preventLeaving/preventLeaving";
|
|||
import { CanvasCommandBar } from "../editorPage/canvasCommandBar";
|
||||
import { TableView } from "../editorPage/tableView";
|
||||
import "../predict/predictPage.scss";
|
||||
import PredictResult from "../predict/predictResult";
|
||||
import PredictResult, { ITableResultItem } from "../predict/predictResult";
|
||||
import { ILoadFileHelper, ILoadFileResult, LoadFileHelper } from "./LoadFileHelper";
|
||||
import "./prebuiltPredictPage.scss";
|
||||
import { ITableHelper, ITableState, TableHelper } from "./tableHelper";
|
||||
|
@ -83,6 +83,10 @@ export interface IPrebuiltPredictPageState extends ILoadFileResult, ITableState
|
|||
predictionEndpointUrl: string;
|
||||
|
||||
liveMode: boolean;
|
||||
|
||||
viewRegionalTable?: boolean;
|
||||
regionalTableToView?: any;
|
||||
tableTagColor?: string;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: IApplicationState) {
|
||||
|
@ -158,6 +162,10 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
predictionEndpointUrl: "",
|
||||
|
||||
liveMode: true,
|
||||
|
||||
viewRegionalTable: false,
|
||||
regionalTableToView: null,
|
||||
tableTagColor: null,
|
||||
};
|
||||
|
||||
private analyzeResults: any;
|
||||
|
@ -203,7 +211,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
if (prevState.highlightedField !== this.state.highlightedField) {
|
||||
this.setPredictedFieldHighlightStatus(this.state.highlightedField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_prevProps.prebuiltSettings !== this.props.prebuiltSettings) {
|
||||
this.handleUpdateRequestURI();
|
||||
|
@ -229,7 +237,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
!this.state.fileLoaded ||
|
||||
(this.state.withPageRange && !this.state.pageRangeIsValid) ||
|
||||
(needEndPoint && (!this.props.prebuiltSettings?.apiKey ||
|
||||
!this.props.prebuiltSettings?.serviceURI));
|
||||
!this.props.prebuiltSettings?.serviceURI));
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -372,12 +380,20 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
onPredictionClick={this.onPredictionClick}
|
||||
onPredictionMouseEnter={this.onPredictionMouseEnter}
|
||||
onPredictionMouseLeave={this.onPredictionMouseLeave}
|
||||
onTablePredictionClick={this.onTablePredictionClick}
|
||||
/>
|
||||
}
|
||||
{
|
||||
(Object.keys(predictions).length === 0 && this.state.predictionLoaded) &&
|
||||
<div>{strings.prebuiltPredict.noFieldCanBeExtracted}</div>
|
||||
}
|
||||
{this.state.viewRegionalTable &&
|
||||
<TableView
|
||||
handleTableViewClose={this.onTablePredictionClose}
|
||||
tableToView={this.state.regionalTableToView}
|
||||
showToolTips={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Alert
|
||||
|
@ -462,7 +478,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
}
|
||||
if (data.file) {
|
||||
HtmlFileReader.readAsText(data.file)
|
||||
.then(({ content }) => JSON.parse(content as string))
|
||||
.then(({ content }) => JSON.parse(content as string))
|
||||
.then(result => this.setState({
|
||||
currentPage: 1,
|
||||
analyzeResult: null,
|
||||
|
@ -470,7 +486,7 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
fileLoaded: false,
|
||||
}, () => new Promise(() => this.handlePredictionResult(result))
|
||||
.catch(this.handlePredictionError)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleLiveModeToggleChange = (event, checked: boolean) => {
|
||||
|
@ -689,22 +705,22 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
}
|
||||
|
||||
private handlePredictionError = (error) => {
|
||||
let alertMessage = "";
|
||||
if (error.response) {
|
||||
alertMessage = error.response.data;
|
||||
} else if (error.errorCode === ErrorCode.PredictWithoutTrainForbidden) {
|
||||
alertMessage = strings.errors.predictWithoutTrainForbidden.message;
|
||||
} else if (error.errorCode === ErrorCode.ModelNotFound) {
|
||||
alertMessage = error.message;
|
||||
} else {
|
||||
alertMessage = interpolate(strings.errors.endpointConnectionError.message, { endpoint: "form recognizer backend URL" });
|
||||
}
|
||||
this.setState({
|
||||
shouldShowAlert: true,
|
||||
alertTitle: "Prediction Failed",
|
||||
alertMessage,
|
||||
isPredicting: false,
|
||||
});
|
||||
let alertMessage = "";
|
||||
if (error.response) {
|
||||
alertMessage = error.response.data;
|
||||
} else if (error.errorCode === ErrorCode.PredictWithoutTrainForbidden) {
|
||||
alertMessage = strings.errors.predictWithoutTrainForbidden.message;
|
||||
} else if (error.errorCode === ErrorCode.ModelNotFound) {
|
||||
alertMessage = error.message;
|
||||
} else {
|
||||
alertMessage = interpolate(strings.errors.endpointConnectionError.message, { endpoint: "form recognizer backend URL" });
|
||||
}
|
||||
this.setState({
|
||||
shouldShowAlert: true,
|
||||
alertTitle: "Prediction Failed",
|
||||
alertMessage,
|
||||
isPredicting: false,
|
||||
});
|
||||
}
|
||||
|
||||
private handleClick = () => {
|
||||
|
@ -810,72 +826,89 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
private drawPredictionResult = (): void => {
|
||||
// Comment this line to prevent clear OCR boundary boxes.
|
||||
// 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 imageExtent = [0, 0, this.state.imageWidth, this.state.imageHeight];
|
||||
const ocrForCurrentPage: any = this.getOcrFromAnalyzeResult(this.state.analyzeResult)[this.state.currentPage - 1];
|
||||
const ocrExtent = [0, 0, ocrForCurrentPage.width, ocrForCurrentPage.height];
|
||||
const predictions = this.getPredictionsFromAnalyzeResult(this.state.analyzeResult);
|
||||
for (const fieldName of Object.keys(predictions)) {
|
||||
const field = predictions[fieldName];
|
||||
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 predictions = this.flatFields(this.getPredictionsFromAnalyzeResult(this.state.analyzeResult));
|
||||
for (const [fieldName, field] of Object.entries(predictions)) {
|
||||
createFeature(fieldName, field);
|
||||
}
|
||||
this.imageMap.addFeatures(features);
|
||||
this.tableHelper.drawTables(this.state.currentPage);
|
||||
}
|
||||
|
||||
private getPredictionsFromAnalyzeResult(analyzeResult: any) {
|
||||
if (analyzeResult) {
|
||||
const documentResults = _.get(analyzeResult, "documentResults", []);
|
||||
const isSupportField = fieldName => {
|
||||
// Define list of unsupported field names.
|
||||
const blockedFieldNames = ["ReceiptType"];
|
||||
return blockedFieldNames.indexOf(fieldName) === -1;
|
||||
}
|
||||
const isRootItemObject = obj => obj.hasOwnProperty("text");
|
||||
// flat fieldProps of type "array" and "object", and extract root level field props in "object" type
|
||||
const flattedFields = {};
|
||||
const flatFields = (fields = {}) => {
|
||||
const flatFieldProps = (displayName, fieldProps) => {
|
||||
if (isSupportField(displayName)) {
|
||||
switch (_.get(fieldProps, "type", "")) {
|
||||
case "array": {
|
||||
const valueArray = _.get(fieldProps, "valueArray", []);
|
||||
for (const [index, valueArrayItem] of valueArray.entries()) {
|
||||
flatFieldProps(`${displayName} ${index + 1}`, valueArrayItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "object": {
|
||||
// root level field props
|
||||
const { type, valueObject, ...restProps } = fieldProps;
|
||||
if (isRootItemObject(restProps)) {
|
||||
flatFieldProps(displayName, restProps);
|
||||
}
|
||||
for (const [fieldName, objFieldProps] of Object.entries(fieldProps.valueObject)) {
|
||||
flatFieldProps(`${displayName}: ${fieldName}`, objFieldProps);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
flattedFields[displayName] = fieldProps;
|
||||
private flatFields = (fields: object = {}): { [key: string]: (object[] | object) } => {
|
||||
/**
|
||||
* @param fields: primitive types, object types likes array, object, and root level field
|
||||
* @return flattenfields, a field props or an array of field props
|
||||
*/
|
||||
const flattedFields = {};
|
||||
const isSupportField = fieldName => {
|
||||
// Define list of unsupported field names.
|
||||
const blockedFieldNames = ["ReceiptType"];
|
||||
return blockedFieldNames.indexOf(fieldName) === -1;
|
||||
}
|
||||
const isRootItemObject = obj => obj.hasOwnProperty("text");
|
||||
// flat fieldProps of type "array" and "object", and extract root level field props in "object" type
|
||||
const flatFieldProps = (fieldName, fieldProps) => {
|
||||
if (isSupportField(fieldName)) {
|
||||
switch (_.get(fieldProps, "type", "")) {
|
||||
case "array": {
|
||||
const valueArray = _.get(fieldProps, "valueArray", []);
|
||||
for (const arrayItem of valueArray) {
|
||||
flatFieldProps(fieldName, arrayItem);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "object": {
|
||||
// root level field props
|
||||
const { type, valueObject, ...restProps } = fieldProps;
|
||||
if (isRootItemObject(restProps)) {
|
||||
flatFieldProps(fieldName, restProps);
|
||||
}
|
||||
for (const objFieldProps of Object.values(fieldProps.valueObject)) {
|
||||
if (objFieldProps) {
|
||||
flatFieldProps(fieldName, objFieldProps);
|
||||
}
|
||||
}
|
||||
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"];
|
||||
flatFields(fields);
|
||||
}
|
||||
return flattedFields;
|
||||
}
|
||||
for (const [fieldName, fieldProps] of Object.entries(fields)) {
|
||||
flatFieldProps(fieldName, fieldProps);
|
||||
}
|
||||
return flattedFields;
|
||||
}
|
||||
|
||||
private getPredictionsFromAnalyzeResult(analyzeResult: any) {
|
||||
if (analyzeResult) {
|
||||
const documentResults = _.get(analyzeResult, "documentResults", []);
|
||||
return documentResults.reduce((accFields, documentResult) => ({ ...accFields, ...documentResult.fields }), {});
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
@ -997,4 +1030,45 @@ export class PrebuiltPredictPage extends React.Component<IPrebuiltPredictPagePro
|
|||
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 RecentModelsView from "./recentModelsView";
|
||||
import { UploadToTrainingSetView } from "./uploadToTrainingSetView";
|
||||
import RegionalTable from "../../common/regionalTable/regionalTable";
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = constants.pdfjsWorkerSrc(pdfjsLib.version);
|
||||
|
||||
|
@ -441,7 +442,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
{this.state.viewRegionalTable &&
|
||||
<div className="m-2">
|
||||
<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
|
||||
className="mt-4 ml-2"
|
||||
theme={getPrimaryGreyTheme()}
|
||||
|
@ -1072,158 +1078,6 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
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) => {
|
||||
this.setState({
|
||||
highlightedField: predictedItem.fieldName ?? "",
|
||||
|
@ -1376,4 +1230,12 @@ export default class PredictPage extends React.Component<IPredictPageProps, IPre
|
|||
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 });
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче