2024-02-01 10:07:38 +00:00
import { t } from '@lingui/macro' ;
2024-03-08 10:47:12 +00:00
import { Button , Input , Table , Tag , DatePicker , Form , Modal , Select , Spin , message , Upload } from 'antd' ;
import React , { useContext , useEffect , useRef , useState } from 'react' ;
import type { GetRef } from 'antd' ;
import { Layout } from 'antd' ;
import {
DownloadOutlined , CheckCircleOutlined ,
ClockCircleFilled ,
} from '@ant-design/icons' ;
import styled from 'styled-components' ;
const { Sider , Content } = Layout ;
import { baseURL } from "request/api" ;
import { useHotkeys } from "react-hotkeys-hook" ;
import { Viewer } from '@react-pdf-viewer/core' ;
2024-02-01 10:07:38 +00:00
import { SbtPageHeader } from 'components/page-header' ;
import { UploadOutlined } from '@ant-design/icons' ;
import type { GetProp , UploadFile , UploadProps } from 'antd' ;
import 'react-json-view-lite/dist/index.css' ;
2024-01-31 04:08:20 +00:00
2024-02-01 10:07:38 +00:00
type FileType = Parameters < GetProp < UploadProps , ' beforeUpload ' > > [ 0 ] ;
2024-03-08 10:47:12 +00:00
const ENABLE_REVIEW = false ;
// Import the styles
import '@react-pdf-viewer/core/lib/styles/index.css' ;
const siderStyle : React.CSSProperties = {
backgroundColor : '#fafafa' ,
padding : 10 ,
width : 200 ,
} ;
const StyledTable = styled ( Table ) `
& . sbt - table - cell {
padding : 4px ! important ;
}
` ;
type InputRef = GetRef < typeof Input > ;
type FormInstance < T > = GetRef < typeof Form < T > > ;
const EditableContext = React . createContext < FormInstance < any > | null > ( null ) ;
interface Item {
key : string ;
accuracy : number ;
revised : string ;
action : string ;
}
interface EditableRowProps {
index : number ;
}
const EditableRow : React.FC < EditableRowProps > = ( { index , . . . props } ) = > {
const [ form ] = Form . useForm ( ) ;
return (
< Form form = { form } component = { false } >
< EditableContext.Provider value = { form } >
< tr { ...props } / >
< / EditableContext.Provider >
< / Form >
) ;
} ;
interface EditableCellProps {
title : React.ReactNode ;
editable : boolean ;
children : React.ReactNode ;
dataIndex : keyof Item ;
record : Item ;
handleSave : ( record : Item ) = > void ;
}
const EditableCell : React.FC < EditableCellProps > = ( {
title ,
editable ,
children ,
dataIndex ,
record ,
handleSave ,
. . . restProps
} ) = > {
const [ editing , setEditing ] = useState ( false ) ;
const inputRef = useRef < InputRef > ( null ) ;
const form = useContext ( EditableContext ) ! ;
useEffect ( ( ) = > {
if ( editing ) {
inputRef . current ! . focus ( ) ;
}
} , [ editing ] ) ;
const toggleEdit = ( ) = > {
setEditing ( ! editing ) ;
form . setFieldsValue ( { [ dataIndex ] : record [ dataIndex ] } ) ;
} ;
const save = async ( ) = > {
try {
const values = await form . validateFields ( ) ;
toggleEdit ( ) ;
handleSave ( { . . . record , . . . values } ) ;
} catch ( errInfo ) {
console . log ( 'Save failed:' , errInfo ) ;
}
} ;
let childNode = children ;
if ( editable ) {
childNode = editing ? (
< Form.Item
style = { { margin : 0 } }
name = { dataIndex }
rules = { [
{
required : true ,
message : ` ${ title } is required. ` ,
} ,
] }
>
< Input ref = { inputRef } onPressEnter = { save } onBlur = { save } / >
< / Form.Item >
) : (
< div className = "editable-cell-value-wrap" style = { { paddingRight : 24 } } onClick = { toggleEdit } >
{ children }
< / div >
) ;
}
return < td { ...restProps } > { childNode } < / td > ;
} ;
// type EditableTableProps = Parameters<typeof Table>[0];
const FileCard = ( { file , isSelected , onClick , setIsReasonModalOpen } ) = > {
const fileName = file [ "File Name" ] ;
return (
< div style = { {
border : '1px solid #ccc' ,
width : '200px' ,
backgroundColor : isSelected ? '#d4ecff' : '#fff' ,
padding : '4px 8px' ,
marginRight : '4px' ,
marginTop : '2px' ,
position : 'relative' ,
height : '100px' ,
overflow : 'hidden' ,
} } onClick = { onClick } >
< div >
< span style = { {
fontSize : '12px' ,
color : '#333' ,
fontWeight : 'bold' ,
padding : '4px 8px' ,
cursor : 'default' ,
} } > { file [ "Doc Type" ] . toUpperCase ( ) } < / span >
< span style = { {
fontSize : '12px' ,
color : '#aaa' ,
fontWeight : 'bold' ,
padding : '4px 8px' ,
cursor : 'default' ,
maxWidth : '50px' ,
overflow : 'hidden' ,
textOverflow : 'ellipsis' ,
} } >
{ fileName ? fileName . substring ( 0 , 25 ) . replace ( "temp_" , "" ) : fileName }
< / span >
< / div >
< div style = { {
padding : '4px' ,
position : 'absolute' ,
bottom : 2 ,
right : 2 ,
} } >
< Button style = { {
margin : '4px 2px' ,
} }
onClick = { ( ) = > {
setIsReasonModalOpen ( true ) ;
} }
>
Review
< / Button >
< Button style = { {
margin : '4px 2px' ,
} } onClick = { ( ) = > {
const downloadUrl = file [ "File URL" ] ;
window . open ( downloadUrl , '_blank' ) ;
} } >
< DownloadOutlined / >
< / Button >
< / div >
< / div >
) ;
} ;
const fetchRequest = async ( id ) = > {
const token = localStorage . getItem ( 'sbt-token' ) || '' ;
const response = await fetch ( ` ${ baseURL } /ctel/request/ ${ id } / ` , {
method : 'GET' ,
headers : {
"Authorization" : ` ${ JSON . parse ( token ) } `
}
} ) ;
return await ( await response . json ( ) ) . subscription_requests [ 0 ] ;
} ;
2024-02-01 10:07:38 +00:00
const InferencePage = ( ) = > {
const [ invoiceFiles , setInvoiceFiles ] = useState < UploadFile [ ] > ( [ ] ) ;
const [ imei1Files , setImei1Files ] = useState < UploadFile [ ] > ( [ ] ) ;
const [ imei2Files , setImei2Files ] = useState < UploadFile [ ] > ( [ ] ) ;
const [ uploading , setUploading ] = useState ( false ) ;
2024-03-08 10:47:12 +00:00
const [ responseData , setResponseData ] = useState ( null ) ;
2024-02-01 10:07:38 +00:00
const [ finishedProcessing , setFinishedProcessing ] = useState ( false ) ;
const handleUpload = ( ) = > {
const formData = new FormData ( ) ;
if ( invoiceFiles . length > 0 ) {
formData . append ( 'invoice_file' , invoiceFiles [ 0 ] as FileType ) ;
}
if ( imei1Files . length > 0 ) {
formData . append ( 'imei_files' , imei1Files [ 0 ] as FileType ) ;
}
if ( imei2Files . length > 0 ) {
formData . append ( 'imei_files' , imei2Files [ 0 ] as FileType ) ;
}
formData . append ( 'is_test_request' , 'true' ) ;
setUploading ( true ) ;
2024-03-08 10:47:12 +00:00
setResponseData ( null ) ;
2024-02-01 10:07:38 +00:00
const token = localStorage . getItem ( 'sbt-token' ) || '' ;
fetch ( ` ${ baseURL } /ctel/images/process_sync/ ` , {
method : 'POST' ,
body : formData ,
headers : {
"Authorization" : ` ${ JSON . parse ( token ) } `
}
} )
2024-03-08 10:47:12 +00:00
. then ( async ( res ) = > {
2024-02-01 10:07:38 +00:00
const data = await res . json ( ) ;
2024-03-08 10:47:12 +00:00
if ( data [ "status" ] != "200" ) {
setResponseData ( null ) ;
return ;
}
loadRequestById ( data [ "request_id" ] ) ;
2024-02-01 10:07:38 +00:00
setFinishedProcessing ( true ) ;
return data ;
} )
. then ( ( ) = > {
message . success ( 'Upload successfully.' ) ;
} )
2024-03-08 10:47:12 +00:00
. catch ( ( e ) = > {
console . log ( e ) ;
2024-02-01 10:07:38 +00:00
message . error ( 'Upload failed.' ) ;
} )
. finally ( ( ) = > {
setUploading ( false ) ;
} ) ;
} ;
2024-03-08 10:47:12 +00:00
const [ loading , setLoading ] = useState ( false ) ;
const [ isReasonModalOpen , setIsReasonModalOpen ] = useState ( false ) ;
const [ selectedFileId , setSelectedFileId ] = useState ( 0 ) ;
const [ selectedFileData , setSelectedFileData ] = useState ( null ) ;
const [ selectedFileName , setSelectedFileName ] = useState ( null ) ;
const [ currentRequest , setCurrentRequest ] = useState ( null ) ;
const [ totalRequests , setTotalPages ] = useState ( 0 ) ;
const [ dataSource , setDataSource ] = useState ( [ ] ) ;
2024-02-01 10:07:38 +00:00
2024-03-08 10:47:12 +00:00
const setAndLoadSelectedFile = async ( requestData , index ) = > {
setSelectedFileId ( index ) ;
if ( ! requestData [ "Files" ] [ index ] ) {
setSelectedFileData ( "FAILED_TO_LOAD_FILE" ) ;
return ;
} ;
const fileName = requestData [ "Files" ] [ index ] [ "File Name" ] ;
const fileURL = requestData [ "Files" ] [ index ] [ "File URL" ] ;
const response = await fetch ( fileURL ) ;
if ( response . status === 200 ) {
setSelectedFileName ( fileName ) ;
setSelectedFileData ( fileURL ) ;
console . log ( "Loading file: " + fileName ) ;
console . log ( "URL: " + fileURL ) ;
} else {
setSelectedFileData ( "FAILED_TO_LOAD_FILE" ) ;
}
} ;
const loadRequestById = ( requestID ) = > {
setLoading ( true ) ;
const requestData = fetchRequest ( requestID ) ;
requestData . then ( async ( data ) = > {
if ( data ) setCurrentRequest ( data ) ;
const predicted = ( data && data [ "Reviewed Result" ] ) ? data [ "Reviewed Result" ] : { } ;
const revised = ( data && data [ "Reviewed Result" ] ) ? data [ "Reviewed Result" ] : { } ;
const keys = Object . keys ( predicted ) ;
const tableRows = [ ] ;
for ( let i = 0 ; i < keys . length ; i ++ ) {
let instance = { } ;
instance [ "key" ] = keys [ i ] ;
instance [ "predicted" ] = predicted [ keys [ i ] ] ;
instance [ "revised" ] = revised [ keys [ i ] ] ;
tableRows . push ( instance ) ;
}
setDataSource ( tableRows ) ;
setLoading ( false ) ;
setAndLoadSelectedFile ( data , 0 ) ;
} ) . finally ( ( ) = > {
setLoading ( false ) ;
} ) ;
} ;
const components = {
body : {
row : EditableRow ,
cell : EditableCell ,
} ,
} ;
// "Key", "Accuracy", "Revised"
interface DataType {
key : string ;
accuracy : number ;
revised : string ;
} ;
const updateRevisedData = async ( newRevisedData : any ) = > {
const requestID = newRevisedData . request_id ;
const token = localStorage . getItem ( 'sbt-token' ) || '' ;
await fetch ( ` ${ baseURL } /ctel/request/ ${ requestID } / ` , {
method : 'POST' ,
headers : {
"Authorization" : ` ${ JSON . parse ( token ) } ` ,
"Content-Type" : "application/json" ,
} ,
body : JSON.stringify ( {
"reviewed_result" : newRevisedData
} ) ,
} ) . catch ( ( error ) = > {
console . log ( error ) ;
message . error ( "Could not update revised data" ) ;
} ) ;
} ;
const handleSave = ( row : DataType ) = > {
const newData = [ . . . dataSource ] ;
const index = newData . findIndex ( ( item ) = > row . key === item . key ) ;
const item = newData [ index ] ;
newData . splice ( index , 1 , {
. . . item ,
. . . row ,
} ) ;
setDataSource ( newData ) ;
const newRevisedData = { } ;
for ( let i = 0 ; i < newData . length ; i ++ ) {
newRevisedData [ newData [ i ] . key ] = newData [ i ] . revised ;
}
updateRevisedData ( newRevisedData ) . then ( ( ) = > {
// "[Is Reviewed]" => true
setCurrentRequest ( {
. . . currentRequest ,
[ "Is Reviewed" ] : true ,
} )
} )
} ;
const submitRevisedData = async ( ) = > {
const newData = [ . . . dataSource ] ;
const newRevisedData = { } ;
for ( let i = 0 ; i < newData . length ; i ++ ) {
newRevisedData [ newData [ i ] . key ] = newData [ i ] . revised ;
}
console . log ( currentRequest )
updateRevisedData ( newRevisedData ) . then ( ( ) = > {
// "[Is Reviewed]" => true
setCurrentRequest ( {
. . . currentRequest ,
[ "Is Reviewed" ] : true ,
} )
} )
} ;
const defaultColumns = [
{
title : 'Key' ,
dataIndex : 'key' ,
key : 'key' ,
width : 200 ,
} ,
{
title : 'Predicted' ,
dataIndex : 'predicted' ,
key : 'predicted' ,
} ,
{
title : ( < div style = { {
width : 120 ,
display : 'flex' ,
lineHeight : '32px' ,
marginLeft : 10 ,
} } > Revised & nbsp ; & nbsp ;
{ ENABLE_REVIEW && < Button
onClick = { ( ) = > {
if ( ! dataSource || ! dataSource . length ) return ;
setDataSource ( dataSource . map ( item = > {
item . revised = item . predicted ;
return item ;
} ) ) ;
setTimeout ( ( ) = > {
submitRevisedData ( ) ;
} , 1000 ) ;
2024-02-01 10:07:38 +00:00
} }
>
2024-03-08 10:47:12 +00:00
Copy Predicted
< / Button > }
< / div > ) ,
dataIndex : 'revised' ,
key : 'revised' ,
editable : ENABLE_REVIEW ,
} ,
] ;
const columns = defaultColumns . map ( ( col ) = > {
if ( ! col . editable ) {
return col ;
}
return {
. . . col ,
onCell : ( record : DataType ) = > ( {
record ,
editable : col.key != "request_id" && col . editable ,
dataIndex : col.dataIndex ,
title : col.title ,
handleSave ,
} ) ,
} ;
} ) ;
const fileExtension = selectedFileName ? selectedFileName . split ( '.' ) . pop ( ) : '' ;
return (
< >
2024-02-01 10:07:38 +00:00
< div style = { {
2024-03-08 10:47:12 +00:00
height : '100%' ,
position : 'relative' ,
2024-02-01 10:07:38 +00:00
} } >
2024-03-08 10:47:12 +00:00
< SbtPageHeader
title = { t ` Inference ` }
/ >
< div style = { {
display : 'flex' ,
flexDirection : 'row' ,
} } >
< div style = { {
paddingTop : "0.5rem" ,
padding : "0.5rem" ,
height : "80px" ,
} } >
< Upload
onRemove = { ( file ) = > {
if ( finishedProcessing ) return ;
setInvoiceFiles ( [ ] )
} }
beforeUpload = { ( file ) = > {
if ( finishedProcessing ) return ;
setInvoiceFiles ( [ file ] )
return false ;
} }
fileList = { invoiceFiles }
>
Invoice : < Button disabled = { finishedProcessing } icon = { < UploadOutlined / > } > Select Image / PDF < / Button >
< / Upload >
< / div >
< div style = { {
paddingTop : "0.5rem" ,
padding : "0.5rem" ,
height : "80px" ,
} } >
< Upload
onRemove = { ( file ) = > {
if ( finishedProcessing ) return ;
setImei1Files ( [ ] )
} }
beforeUpload = { ( file ) = > {
if ( finishedProcessing ) return ;
setImei1Files ( [ file ] )
return false ;
} }
fileList = { imei1Files }
>
IMEI 1 : < Button disabled = { finishedProcessing } icon = { < UploadOutlined / > } > Select Image < / Button >
< / Upload >
< / div >
< div style = { {
paddingTop : "0.5rem" ,
padding : "0.5rem" ,
height : "80px" ,
} } >
< Upload
onRemove = { ( file ) = > {
if ( finishedProcessing ) return ;
setImei2Files ( [ ] )
} }
beforeUpload = { ( file ) = > {
if ( finishedProcessing ) return ;
setImei2Files ( [ file ] )
return false ;
} }
fileList = { imei2Files }
>
IMEI 2 : < Button disabled = { finishedProcessing } icon = { < UploadOutlined / > } > Select Image < / Button >
< / Upload >
< / div >
< div style = { {
paddingTop : "0.5rem" ,
padding : "0.5rem" ,
height : "80px" ,
display : 'flex' ,
flexDirection : 'row' ,
} } >
{ ! finishedProcessing && < Button
type = "primary"
onClick = { handleUpload }
disabled = { imei1Files . length === 0 && imei2Files . length === 0 }
loading = { uploading }
>
{ uploading ? 'Uploading' : 'Process Data' }
< / Button > }
{ finishedProcessing && < Button
type = "primary"
onClick = { ( ) = > {
setFinishedProcessing ( false ) ;
setResponseData ( null ) ;
setInvoiceFiles ( [ ] ) ;
setImei1Files ( [ ] ) ;
setImei2Files ( [ ] ) ;
} }
>
Reset
< / Button > }
< / div >
< / div >
< div
style = { { height : '100%' , position : 'absolute' , top : 0 , left : 0 , width : '100%' , background : "#00000033" , zIndex : 1000 , display : loading ? 'block' : 'none' } }
2024-02-01 10:07:38 +00:00
>
2024-03-08 10:47:12 +00:00
< Spin spinning = { true } style = { {
position : 'absolute' ,
top : '50%' ,
left : '50%' ,
marginTop : - 12 ,
marginLeft : - 12 ,
width : 24 ,
height : 24 ,
borderRadius : '50%' ,
} } size = 'large' / >
< / div >
< Layout style = { {
overflow : 'auto' ,
width : '100%' ,
height : '100%' ,
maxWidth : '100%' ,
minHeight : '70%' ,
maxHeight : '70%' ,
display : 'flex' ,
padding : '8px' ,
} } >
< Content style = { {
textAlign : 'center' ,
color : '#fff' ,
backgroundColor : '#efefef' ,
height : '100%' ,
display : 'flex' ,
flexDirection : 'row' ,
} } >
{ currentRequest ? . Files ? . length && < div
style = { {
width : "200px" ,
display : "flex" ,
flexDirection : "column" ,
flexGrow : 0 ,
} } >
< h2
style = { {
color : "#333" ,
padding : 10 ,
fontWeight : 'bold' ,
fontSize : 18 ,
textTransform : 'uppercase'
} }
> Files ( { currentRequest ? . Files ? . length } ) < / h2 >
{ currentRequest ? . Files . map ( ( file , index ) = > (
< FileCard key = { index } file = { file } isSelected = { index === selectedFileId } onClick = {
( ) = > {
setAndLoadSelectedFile ( currentRequest , index ) ;
}
} setIsReasonModalOpen = { setIsReasonModalOpen } / >
) ) }
< / div > }
{ selectedFileData && < div style = { {
border : "1px solid #ccc" ,
flexGrow : 1 ,
height : '100%' ,
} } >
{ selectedFileData === "FAILED_TO_LOAD_FILE" ? < p style = { { color : "#333" } } > < / p > : ( fileExtension === "pdf" ? ( < Viewer
fileUrl = { selectedFileData }
/ > ) : < d i v s t y l e = { {
height : "100%" ,
width : "100%" ,
overflow : "auto" ,
} } > < img width = { "100%" } src = { selectedFileData } alt = "file" / > < / div > ) }
< / div > }
< / Content >
< Sider width = "400px" style = { siderStyle } >
< div >
< Input size = 'small' addonBefore = "Request ID" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest . RequestID : "" } / >
< Input size = 'small' addonBefore = "Redemption" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? . RedemptionID ? currentRequest . RedemptionID : "" } / >
< Input size = 'small' addonBefore = "Uploaded date" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest . created_at : "" } / >
< Input size = 'small' addonBefore = "Request time" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest [ "Client Request Time (ms)" ] : "" } / >
< Input size = 'small' addonBefore = "Processing time" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest [ "Server Processing Time (ms)" ] : "" } / >
< div style = { { marginBottom : "8px" , marginTop : "8px" , display : "flex" } } >
{ currentRequest && ( currentRequest [ "Is Reviewed" ] ? < Tag icon = { < CheckCircleOutlined / > } color = "success" style = { { padding : "4px 16px" } } >
Reviewed
< / Tag > : < Tag icon = { < ClockCircleFilled / > } color = "warning" style = { { padding : "4px 16px" } } >
Not Reviewed
< / Tag > ) }
< / div >
< / div >
< / Sider >
< / Layout >
< Modal
title = { t ` Review ` }
open = { isReasonModalOpen }
width = { 700 }
onOk = {
( ) = > {
}
}
onCancel = {
( ) = > {
setIsReasonModalOpen ( false ) ;
}
}
2024-02-01 10:07:38 +00:00
>
2024-03-08 10:47:12 +00:00
< Form
style = { {
marginTop : 30 ,
} }
>
< Form.Item
name = 'reason'
label = { t ` Reason for bad quality: ` }
style = { {
marginBottom : 10 ,
} }
>
< Select
placeholder = 'Select a reason'
style = { { width : 200 } }
options = { [
{ value : 'invalid_image' , label : t ` Invalid image ` } ,
{ value : 'missing_information' , label : t ` Missing information ` } ,
{ value : 'too_blurry_text' , label : t ` Too blurry text ` } ,
{ value : 'too_small_text' , label : t ` Too small text ` } ,
{ value : 'handwritten' , label : t ` Handwritten ` } ,
{ value : 'recheck' , label : t ` Recheck ` } ,
] }
/ >
< / Form.Item >
< / Form >
< / Modal >
< StyledTable components = { components }
rowClassName = { ( ) = > 'editable-row' }
bordered dataSource = { dataSource } columns = { columns }
/ >
2024-02-01 10:07:38 +00:00
< / div >
< / >
) ;
} ;
export default InferencePage ;