IO-3515 resolve issues on search selects not updating, improve confidence scoring.
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { Button } from "antd";
|
||||
import { Button, Tag, Modal, Typography } from "antd";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { FaWandMagicSparkles } from "react-icons/fa6";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext";
|
||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { FaWandMagicSparkles } from "react-icons/fa6";
|
||||
import { useApolloClient } from "@apollo/client/react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
billEnterModal: selectBillEnterModal,
|
||||
@@ -24,11 +26,31 @@ function BillEnterAiScan({
|
||||
setIsAiScan
|
||||
}) {
|
||||
const notification = useNotification();
|
||||
const [showBetaModal, setShowBetaModal] = useState(false);
|
||||
const BETA_ACCEPTANCE_KEY = "ai_scan_beta_acceptance";
|
||||
const client = useApolloClient();
|
||||
const handleBetaAcceptance = () => {
|
||||
localStorage.setItem(BETA_ACCEPTANCE_KEY, "true");
|
||||
setShowBetaModal(false);
|
||||
// Trigger the file input after acceptance
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const checkBetaAcceptance = () => {
|
||||
const hasAccepted = localStorage.getItem(BETA_ACCEPTANCE_KEY);
|
||||
if (hasAccepted) {
|
||||
// User has already accepted, proceed with file selection
|
||||
fileInputRef.current?.click();
|
||||
} else {
|
||||
// Show beta modal
|
||||
setShowBetaModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Polling function for multipage PDF status
|
||||
const pollJobStatus = async (jobId) => {
|
||||
const pollJobStatus = async (textractJobId) => {
|
||||
try {
|
||||
const { data } = await axios.get(`/ai/bill-ocr/status/${jobId}`);
|
||||
const { data } = await axios.get(`/ai/bill-ocr/status/${textractJobId}`);
|
||||
|
||||
if (data.status === "COMPLETED") {
|
||||
// Stop polling
|
||||
@@ -41,6 +63,7 @@ function BillEnterAiScan({
|
||||
// Update form with the extracted data
|
||||
if (data.data && data.data.billForm) {
|
||||
form.setFieldsValue(data.data.billForm);
|
||||
await form.validateFields(["billlines"], { recursive: true });
|
||||
notification.success({
|
||||
title: "AI Scan Complete",
|
||||
message: "Invoice data has been extracted successfully"
|
||||
@@ -82,69 +105,92 @@ function BillEnterAiScan({
|
||||
type="file"
|
||||
accept="image/*,application/pdf"
|
||||
style={{ display: "none" }}
|
||||
onChange={(e) => {
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setScanLoading(true);
|
||||
setIsAiScan(true);
|
||||
const formdata = new FormData();
|
||||
formdata.append("billScan", file);
|
||||
formdata.append("jobid", billEnterModal.context.job.id);
|
||||
formdata.append("jobid", billEnterModal.context.job?.id);
|
||||
formdata.append("bodyshopid", bodyshop.id);
|
||||
formdata.append("partsorderid", billEnterModal.context.parts_order?.id);
|
||||
//formdata.append("skipTextract", "true"); // For testing purposes
|
||||
axios
|
||||
.post("/ai/bill-ocr", formdata)
|
||||
.then(({ data, status }) => {
|
||||
if (status === 202) {
|
||||
// Multipage PDF - start polling
|
||||
notification.info({
|
||||
title: "Processing Invoice",
|
||||
message: "This is a multipage document. Processing may take a few moments..."
|
||||
});
|
||||
|
||||
//Workaround needed to bypass react-compiler error about manipulating refs in child components. Refactor may be needed in the future to clean this up.
|
||||
setPollingIntervalRef(
|
||||
setInterval(() => {
|
||||
pollJobStatus(data.jobId);
|
||||
}, 3000)
|
||||
);
|
||||
try {
|
||||
const { data, status } = await axios.post("/ai/bill-ocr", formdata);
|
||||
|
||||
// Initial poll
|
||||
pollJobStatus(data.jobId);
|
||||
} else if (status === 200) {
|
||||
// Single page - immediate response
|
||||
setScanLoading(false);
|
||||
|
||||
form.setFieldsValue(data.data.billForm);
|
||||
notification.success({
|
||||
title: "AI Scan Complete",
|
||||
message: "Invoice data has been extracted successfully"
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setScanLoading(false);
|
||||
notification.error({
|
||||
title: "AI Scan Failed",
|
||||
message: error.response?.data?.message || error.message || "Failed to process invoice"
|
||||
if (status === 202) {
|
||||
// Multipage PDF - start polling
|
||||
notification.info({
|
||||
title: "Processing Invoice",
|
||||
message: "This is a multipage document. Processing may take a few moments..."
|
||||
});
|
||||
|
||||
//Workaround needed to bypass react-compiler error about manipulating refs in child components. Refactor may be needed in the future to clean this up.
|
||||
setPollingIntervalRef(
|
||||
setInterval(() => {
|
||||
pollJobStatus(data.textractJobId);
|
||||
}, 3000)
|
||||
);
|
||||
|
||||
// Initial poll
|
||||
pollJobStatus(data.textractJobId);
|
||||
} else if (status === 200) {
|
||||
// Single page - immediate response
|
||||
setScanLoading(false);
|
||||
|
||||
form.setFieldsValue(data.data.billForm);
|
||||
await form.validateFields(["billlines"], { recursive: true });
|
||||
|
||||
notification.success({
|
||||
title: "AI Scan Complete",
|
||||
message: "Invoice data has been extracted successfully"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setScanLoading(false);
|
||||
notification.error({
|
||||
title: "AI Scan Failed",
|
||||
message: error.response?.data?.message || error.message || "Failed to process invoice"
|
||||
});
|
||||
}
|
||||
}
|
||||
// Reset the input so the same file can be selected again
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
fileInputRef.current?.click();
|
||||
}}
|
||||
icon={<FaWandMagicSparkles />}
|
||||
loading={scanLoading}
|
||||
disabled={scanLoading}
|
||||
>
|
||||
|
||||
<Button onClick={checkBetaAcceptance} icon={<FaWandMagicSparkles />} loading={scanLoading} disabled={scanLoading}>
|
||||
{scanLoading ? "Processing Invoice..." : "AI Scan"}
|
||||
<Tag color="red">BETA</Tag>
|
||||
</Button>
|
||||
|
||||
<Modal
|
||||
title="AI Scan Beta Disclaimer"
|
||||
open={showBetaModal}
|
||||
onOk={handleBetaAcceptance}
|
||||
onCancel={() => setShowBetaModal(false)}
|
||||
okText="Accept and Continue"
|
||||
cancelText="Cancel"
|
||||
>
|
||||
<Typography.Title level={2}>AI Usage Disclaimer</Typography.Title>
|
||||
|
||||
<Typography.Text>
|
||||
This AI scanning feature is currently in <strong>beta</strong>. While it can accelerate data entry, you{" "}
|
||||
<strong>must carefully review all extracted results</strong> for accuracy.
|
||||
</Typography.Text>
|
||||
<Typography.Text>The AI may make mistakes or miss information. Always verify:</Typography.Text>
|
||||
<ul>
|
||||
<li>All line items and quantities</li>
|
||||
<li>Prices and totals</li>
|
||||
<li>Part numbers and descriptions</li>
|
||||
<li>Any other critical invoice details</li>
|
||||
</ul>
|
||||
<Typography.Text>
|
||||
By continuing, you acknowledge that you will review and verify all AI-generated data before posting.
|
||||
</Typography.Text>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user