github-actions[bot] commited on
Commit
f596af1
·
1 Parent(s): 9c7e72f

Sync from GitHub: 4201f078caa4a5098a2839eba2eab3ab5586890c

Browse files
app.py CHANGED
@@ -279,6 +279,7 @@ async def process_invoice(
279
  "doc_id": result.get("doc_id", doc_id),
280
  "processing_time": result.get("processing_time_sec", 0),
281
  "confidence": result.get("confidence", 0),
 
282
  "fields": fields # Include raw fields for reference
283
  }, media_type="application/json; charset=utf-8")
284
 
 
279
  "doc_id": result.get("doc_id", doc_id),
280
  "processing_time": result.get("processing_time_sec", 0),
281
  "confidence": result.get("confidence", 0),
282
+ "cost_estimate_usd": result.get("cost_estimate_usd", 0),
283
  "fields": fields # Include raw fields for reference
284
  }, media_type="application/json; charset=utf-8")
285
 
frontend/src/App.jsx CHANGED
@@ -16,6 +16,7 @@ function App() {
16
  const [previewImages, setPreviewImages] = useState([]);
17
  const [processingIndex, setProcessingIndex] = useState(null);
18
  const [resolutionMap, setResolutionMap] = useState({});
 
19
 
20
  const handleFilesSelected = async (files) => {
21
  setProcessing(false);
@@ -127,16 +128,15 @@ function App() {
127
  }
128
  };
129
 
130
- const handleReprocess = async (result) => {
131
  const index = results.findIndex(r => r.key === result.key);
132
  if (index === -1) return;
133
 
134
  setProcessingIndex(index);
135
 
136
  try {
137
- // Use current resolution-adjusted image
138
- const processData = resolutionMap[result.key] || { dataUrl: imageDataMap[result.key] };
139
- const blob = dataUrlToBlob(processData.dataUrl);
140
 
141
  const newResult = await processSingleInvoice(blob, result.filename);
142
 
@@ -249,12 +249,36 @@ function App() {
249
 
250
  {/* Progress Indicator */}
251
  {processing && (
252
- <ProgressIndicator
253
- total={progress.total}
254
- completed={progress.completed}
255
- current={progress.current}
256
- results={results}
257
- />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  )}
259
 
260
  {/* Results Section */}
 
16
  const [previewImages, setPreviewImages] = useState([]);
17
  const [processingIndex, setProcessingIndex] = useState(null);
18
  const [resolutionMap, setResolutionMap] = useState({});
19
+ const [resultResolutionMap, setResultResolutionMap] = useState({});
20
 
21
  const handleFilesSelected = async (files) => {
22
  setProcessing(false);
 
128
  }
129
  };
130
 
131
+ const handleReprocess = async (result, resolution, adjustedDataUrl) => {
132
  const index = results.findIndex(r => r.key === result.key);
133
  if (index === -1) return;
134
 
135
  setProcessingIndex(index);
136
 
137
  try {
138
+ // Use resolution-adjusted image from ResultCard
139
+ const blob = dataUrlToBlob(adjustedDataUrl || imageDataMap[result.key]);
 
140
 
141
  const newResult = await processSingleInvoice(blob, result.filename);
142
 
 
249
 
250
  {/* Progress Indicator */}
251
  {processing && (
252
+ <div className="space-y-4">
253
+ <ProgressIndicator
254
+ total={progress.total}
255
+ completed={progress.completed}
256
+ current={progress.current}
257
+ results={results}
258
+ />
259
+
260
+ {/* Show image being processed with scanning animation */}
261
+ {processingIndex !== null && previewImages[processingIndex] && (
262
+ <div className="bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200 p-6">
263
+ <h3 className="text-lg font-semibold text-gray-800 mb-4 flex items-center">
264
+ <svg className="w-5 h-5 mr-2 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24" stroke="currentColor">
265
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
266
+ </svg>
267
+ Processing: {previewImages[processingIndex].filename}
268
+ </h3>
269
+ <div className="relative bg-gray-50 rounded-lg p-4 flex justify-center items-center">
270
+ <img
271
+ src={previewImages[processingIndex].dataUrl}
272
+ alt="Processing"
273
+ className="max-w-full max-h-96 rounded shadow-md"
274
+ />
275
+ <div className="absolute inset-0 flex items-center justify-center bg-black/10 rounded-lg">
276
+ <div className="scanning-line"></div>
277
+ </div>
278
+ </div>
279
+ </div>
280
+ )}
281
+ </div>
282
  )}
283
 
284
  {/* Results Section */}
frontend/src/components/ResultCard.jsx CHANGED
@@ -1,11 +1,15 @@
1
  import React, { useRef, useEffect, useState } from 'react';
 
2
 
3
  const ResultCard = ({ result, imageData, onReprocess, isProcessing }) => {
4
  const canvasRef = useRef(null);
 
5
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
6
  const [signatureCrop, setSignatureCrop] = useState(null);
7
  const [stampCrop, setStampCrop] = useState(null);
8
  const [resolution, setResolution] = useState(100);
 
 
9
 
10
  // Function to crop image regions
11
  const cropRegion = (img, coords, scaleX, scaleY) => {
@@ -122,6 +126,33 @@ const ResultCard = ({ result, imageData, onReprocess, isProcessing }) => {
122
  img.src = imageData;
123
  }, [imageData, result]);
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  if (!result.success) {
126
  return (
127
  <div className="bg-red-50 border-2 border-red-200 rounded-xl p-6 mb-4">
@@ -161,7 +192,7 @@ const ResultCard = ({ result, imageData, onReprocess, isProcessing }) => {
161
  </div>
162
  {onReprocess && (
163
  <button
164
- onClick={() => onReprocess(result)}
165
  disabled={isProcessing}
166
  className="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
167
  >
@@ -183,6 +214,32 @@ const ResultCard = ({ result, imageData, onReprocess, isProcessing }) => {
183
  </svg>
184
  Document Preview
185
  </h4>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  <div className="relative bg-gray-50 rounded-lg p-4 flex justify-center items-center">
187
  <canvas ref={canvasRef} className="max-w-full h-auto rounded shadow-md" />
188
  {isProcessing && (
@@ -231,10 +288,10 @@ const ResultCard = ({ result, imageData, onReprocess, isProcessing }) => {
231
  <div className="text-lg font-bold text-green-600">{(result.confidence * 100).toFixed(1)}%</div>
232
  </div>
233
  )}
234
- {result.fields?.cost_estimate_usd !== undefined && (
235
  <div className="bg-white rounded-lg p-3 shadow-sm text-center">
236
  <div className="text-xs text-gray-500 mb-1">Cost</div>
237
- <div className="text-lg font-bold text-purple-600">${result.fields.cost_estimate_usd.toFixed(4)}</div>
238
  </div>
239
  )}
240
  </div>
@@ -337,6 +394,9 @@ const ResultCard = ({ result, imageData, onReprocess, isProcessing }) => {
337
  </div>
338
  </div>
339
  </div>
 
 
 
340
  </div>
341
  );
342
  };
 
1
  import React, { useRef, useEffect, useState } from 'react';
2
+ import { SlidersHorizontal } from 'lucide-react';
3
 
4
  const ResultCard = ({ result, imageData, onReprocess, isProcessing }) => {
5
  const canvasRef = useRef(null);
6
+ const previewCanvasRef = useRef(null);
7
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
8
  const [signatureCrop, setSignatureCrop] = useState(null);
9
  const [stampCrop, setStampCrop] = useState(null);
10
  const [resolution, setResolution] = useState(100);
11
+ const [adjustedDataUrl, setAdjustedDataUrl] = useState(null);
12
+ const [previewDimensions, setPreviewDimensions] = useState({ width: 0, height: 0 });
13
 
14
  // Function to crop image regions
15
  const cropRegion = (img, coords, scaleX, scaleY) => {
 
126
  img.src = imageData;
127
  }, [imageData, result]);
128
 
129
+ // Handle resolution adjustment for preview
130
+ useEffect(() => {
131
+ if (!imageData || !previewCanvasRef.current) return;
132
+
133
+ const canvas = previewCanvasRef.current;
134
+ const ctx = canvas.getContext('2d');
135
+ const img = new Image();
136
+
137
+ img.onload = () => {
138
+ const scale = resolution / 100;
139
+ const newWidth = Math.floor(img.width * scale);
140
+ const newHeight = Math.floor(img.height * scale);
141
+
142
+ canvas.width = newWidth;
143
+ canvas.height = newHeight;
144
+ setPreviewDimensions({ width: newWidth, height: newHeight });
145
+
146
+ ctx.drawImage(img, 0, 0, newWidth, newHeight);
147
+
148
+ // Generate adjusted data URL
149
+ const adjustedUrl = canvas.toDataURL('image/jpeg', 0.95);
150
+ setAdjustedDataUrl(adjustedUrl);
151
+ };
152
+
153
+ img.src = imageData;
154
+ }, [imageData, resolution]);
155
+
156
  if (!result.success) {
157
  return (
158
  <div className="bg-red-50 border-2 border-red-200 rounded-xl p-6 mb-4">
 
192
  </div>
193
  {onReprocess && (
194
  <button
195
+ onClick={() => onReprocess(result, resolution, adjustedDataUrl)}
196
  disabled={isProcessing}
197
  className="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
198
  >
 
214
  </svg>
215
  Document Preview
216
  </h4>
217
+
218
+ {/* Resolution Slider */}
219
+ <div className="bg-white rounded-lg p-4 shadow-sm border border-gray-200">
220
+ <div className="flex items-center justify-between mb-3">
221
+ <label className="text-sm font-medium text-gray-700 flex items-center gap-2">
222
+ <SlidersHorizontal className="w-4 h-4 text-primary-500" />
223
+ Adjust Resolution for Reprocessing
224
+ </label>
225
+ <span className="text-sm font-semibold text-primary-600">{resolution}%</span>
226
+ </div>
227
+ <input
228
+ type="range"
229
+ min="10"
230
+ max="100"
231
+ step="5"
232
+ value={resolution}
233
+ onChange={(e) => setResolution(parseInt(e.target.value))}
234
+ className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb"
235
+ disabled={isProcessing}
236
+ />
237
+ <div className="flex justify-between text-xs text-gray-500 mt-2">
238
+ <span>10% (Fast)</span>
239
+ <span className="text-gray-400">Dimensions: {previewDimensions.width} × {previewDimensions.height}px</span>
240
+ <span>100% (Best)</span>
241
+ </div>
242
+ </div>
243
  <div className="relative bg-gray-50 rounded-lg p-4 flex justify-center items-center">
244
  <canvas ref={canvasRef} className="max-w-full h-auto rounded shadow-md" />
245
  {isProcessing && (
 
288
  <div className="text-lg font-bold text-green-600">{(result.confidence * 100).toFixed(1)}%</div>
289
  </div>
290
  )}
291
+ {result.cost_estimate_usd !== undefined && (
292
  <div className="bg-white rounded-lg p-3 shadow-sm text-center">
293
  <div className="text-xs text-gray-500 mb-1">Cost</div>
294
+ <div className="text-lg font-bold text-purple-600">${result.cost_estimate_usd.toFixed(4)}</div>
295
  </div>
296
  )}
297
  </div>
 
394
  </div>
395
  </div>
396
  </div>
397
+
398
+ {/* Hidden canvas for resolution preview generation */}
399
+ <canvas ref={previewCanvasRef} style={{ display: 'none' }} />
400
  </div>
401
  );
402
  };