Seth0330 commited on
Commit
da780d4
·
verified ·
1 Parent(s): bb07183

Update frontend/src/components/ocr/ExtractionOutput.jsx

Browse files
frontend/src/components/ocr/ExtractionOutput.jsx CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useState } from "react";
2
  import { motion, AnimatePresence } from "framer-motion";
3
  import {
4
  Code2,
@@ -110,22 +110,110 @@ Support Package 1 $500.00 $500.00
110
  Payment Terms: Net 30
111
  Thank you for your business!`;
112
 
113
- export default function ExtractionOutput({ hasFile, isProcessing, isComplete }) {
114
- const [activeTab, setActiveTab] = useState("text");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  const [copied, setCopied] = useState(false);
116
- const [expandedSections, setExpandedSections] = useState(["vendor", "invoice", "items", "totals"]);
 
 
 
 
 
 
 
 
 
 
117
 
118
  const handleCopy = () => {
119
- const content =
120
- activeTab === "json"
121
- ? JSON.stringify(mockData, null, 2)
122
- : activeTab === "xml"
123
- ? mockXML
124
- : mockText;
 
 
 
125
  navigator.clipboard.writeText(content);
126
  setCopied(true);
127
  setTimeout(() => setCopied(false), 2000);
128
  };
 
 
 
 
 
 
 
129
 
130
  const toggleSection = (section) => {
131
  setExpandedSections((prev) =>
@@ -224,7 +312,9 @@ export default function ExtractionOutput({ hasFile, isProcessing, isComplete })
224
  <div>
225
  <h3 className="font-semibold text-slate-800 text-sm">Extracted Data</h3>
226
  <p className="text-xs text-slate-400">
227
- {isComplete ? "25 fields extracted" : "Waiting for extraction"}
 
 
228
  </p>
229
  </div>
230
  </div>
@@ -309,23 +399,37 @@ export default function ExtractionOutput({ hasFile, isProcessing, isComplete })
309
  </div>
310
  </div>
311
  </div>
 
 
 
 
 
 
 
 
 
 
312
  ) : (
313
  <div className="p-4 font-mono text-sm">
314
  {activeTab === "text" ? (
315
  <pre className="text-sm text-slate-700 whitespace-pre-wrap leading-relaxed">
316
- {mockText}
317
  </pre>
318
  ) : activeTab === "json" ? (
319
  <div className="space-y-1">
320
  <span className="text-slate-400">{"{"}</span>
321
- {Object.entries(mockData).map(([key, value]) =>
322
- renderSection(key, value, 1)
 
 
 
 
323
  )}
324
  <span className="text-slate-400">{"}"}</span>
325
  </div>
326
  ) : (
327
  <pre className="text-sm text-slate-600 whitespace-pre-wrap">
328
- {mockXML.split("\n").map((line, i) => (
329
  <div key={i} className="hover:bg-slate-50 px-2 -mx-2 rounded">
330
  {line.includes("<") ? (
331
  <>
@@ -363,21 +467,28 @@ export default function ExtractionOutput({ hasFile, isProcessing, isComplete })
363
  </div>
364
 
365
  {/* Confidence Footer */}
366
- {isComplete && (
367
  <div className="px-5 py-3 border-t border-slate-100 bg-slate-50/50">
368
  <div className="flex items-center justify-between text-xs">
369
  <div className="flex items-center gap-4">
370
  <div className="flex items-center gap-1.5">
371
- <div className="h-2 w-2 rounded-full bg-emerald-500" />
 
 
 
372
  <span className="text-slate-500">Confidence:</span>
373
- <span className="font-semibold text-slate-700">98%</span>
 
 
374
  </div>
375
  <div className="flex items-center gap-1.5">
376
  <span className="text-slate-500">Fields:</span>
377
- <span className="font-semibold text-slate-700">25</span>
378
  </div>
379
  </div>
380
- <span className="text-slate-400">Processed in 2.3s</span>
 
 
381
  </div>
382
  </div>
383
  )}
 
1
+ import React, { useState, useEffect } from "react";
2
  import { motion, AnimatePresence } from "framer-motion";
3
  import {
4
  Code2,
 
110
  Payment Terms: Net 30
111
  Thank you for your business!`;
112
 
113
+ // Helper function to convert object to XML
114
+ function objectToXML(obj, rootName = "extraction") {
115
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>\n<${rootName}>\n`;
116
+
117
+ const convert = (obj, indent = " ") => {
118
+ for (const [key, value] of Object.entries(obj)) {
119
+ if (value === null || value === undefined) continue;
120
+
121
+ if (Array.isArray(value)) {
122
+ value.forEach((item) => {
123
+ xml += `${indent}<${key}>\n`;
124
+ if (typeof item === "object") {
125
+ convert(item, indent + " ");
126
+ } else {
127
+ xml += `${indent} ${item}\n`;
128
+ }
129
+ xml += `${indent}</${key}>\n`;
130
+ });
131
+ } else if (typeof value === "object") {
132
+ xml += `${indent}<${key}>\n`;
133
+ convert(value, indent + " ");
134
+ xml += `${indent}</${key}>\n`;
135
+ } else {
136
+ xml += `${indent}<${key}>${value}</${key}>\n`;
137
+ }
138
+ }
139
+ };
140
+
141
+ convert(obj);
142
+ xml += `</${rootName}>`;
143
+ return xml;
144
+ }
145
+
146
+ // Helper function to format fields as readable text
147
+ function fieldsToText(fields) {
148
+ if (!fields || typeof fields !== "object") {
149
+ return "No data extracted.";
150
+ }
151
+
152
+ let text = "";
153
+
154
+ const formatValue = (key, value, indent = "") => {
155
+ if (Array.isArray(value)) {
156
+ text += `${indent}${key}:\n`;
157
+ value.forEach((item, idx) => {
158
+ if (typeof item === "object") {
159
+ text += `${indent} Item ${idx + 1}:\n`;
160
+ Object.entries(item).forEach(([k, v]) => formatValue(k, v, indent + " "));
161
+ } else {
162
+ text += `${indent} - ${item}\n`;
163
+ }
164
+ });
165
+ } else if (typeof value === "object" && value !== null) {
166
+ text += `${indent}${key}:\n`;
167
+ Object.entries(value).forEach(([k, v]) => formatValue(k, v, indent + " "));
168
+ } else {
169
+ text += `${indent}${key}: ${value}\n`;
170
+ }
171
+ };
172
+
173
+ Object.entries(fields).forEach(([key, value]) => {
174
+ formatValue(key, value);
175
+ text += "\n";
176
+ });
177
+
178
+ return text.trim() || "No data extracted.";
179
+ }
180
+
181
+ export default function ExtractionOutput({ hasFile, isProcessing, isComplete, extractionResult }) {
182
+ const [activeTab, setActiveTab] = useState("json");
183
  const [copied, setCopied] = useState(false);
184
+
185
+ // Get fields from extraction result, default to empty object
186
+ const fields = extractionResult?.fields || {};
187
+ const confidence = extractionResult?.confidence || 0;
188
+ const fieldsExtracted = extractionResult?.fieldsExtracted || 0;
189
+ const totalTime = extractionResult?.totalTime || 0;
190
+
191
+ // Initialize expanded sections based on available fields
192
+ const [expandedSections, setExpandedSections] = useState(() =>
193
+ Object.keys(fields).slice(0, 5) // Expand first 5 sections by default
194
+ );
195
 
196
  const handleCopy = () => {
197
+ let content = "";
198
+ if (activeTab === "json") {
199
+ content = JSON.stringify(fields, null, 2);
200
+ } else if (activeTab === "xml") {
201
+ content = objectToXML(fields);
202
+ } else {
203
+ content = fieldsToText(fields);
204
+ }
205
+
206
  navigator.clipboard.writeText(content);
207
  setCopied(true);
208
  setTimeout(() => setCopied(false), 2000);
209
  };
210
+
211
+ // Update expanded sections when fields change
212
+ React.useEffect(() => {
213
+ if (extractionResult?.fields) {
214
+ setExpandedSections(Object.keys(extractionResult.fields).slice(0, 5));
215
+ }
216
+ }, [extractionResult]);
217
 
218
  const toggleSection = (section) => {
219
  setExpandedSections((prev) =>
 
312
  <div>
313
  <h3 className="font-semibold text-slate-800 text-sm">Extracted Data</h3>
314
  <p className="text-xs text-slate-400">
315
+ {isComplete
316
+ ? `${fieldsExtracted} field${fieldsExtracted !== 1 ? 's' : ''} extracted`
317
+ : "Waiting for extraction"}
318
  </p>
319
  </div>
320
  </div>
 
399
  </div>
400
  </div>
401
  </div>
402
+ ) : isComplete && Object.keys(fields).length === 0 ? (
403
+ <div className="h-full flex items-center justify-center p-6">
404
+ <div className="text-center">
405
+ <div className="h-20 w-20 mx-auto rounded-2xl bg-amber-100 flex items-center justify-center mb-4">
406
+ <Code2 className="h-10 w-10 text-amber-600" />
407
+ </div>
408
+ <p className="text-slate-600 font-medium mb-1">No data extracted</p>
409
+ <p className="text-slate-400 text-sm">The document may not contain extractable fields</p>
410
+ </div>
411
+ </div>
412
  ) : (
413
  <div className="p-4 font-mono text-sm">
414
  {activeTab === "text" ? (
415
  <pre className="text-sm text-slate-700 whitespace-pre-wrap leading-relaxed">
416
+ {fieldsToText(fields)}
417
  </pre>
418
  ) : activeTab === "json" ? (
419
  <div className="space-y-1">
420
  <span className="text-slate-400">{"{"}</span>
421
+ {Object.keys(fields).length > 0 ? (
422
+ Object.entries(fields).map(([key, value]) =>
423
+ renderSection(key, value, 1)
424
+ )
425
+ ) : (
426
+ <div className="pl-4 text-slate-400 italic">No fields extracted</div>
427
  )}
428
  <span className="text-slate-400">{"}"}</span>
429
  </div>
430
  ) : (
431
  <pre className="text-sm text-slate-600 whitespace-pre-wrap">
432
+ {objectToXML(fields).split("\n").map((line, i) => (
433
  <div key={i} className="hover:bg-slate-50 px-2 -mx-2 rounded">
434
  {line.includes("<") ? (
435
  <>
 
467
  </div>
468
 
469
  {/* Confidence Footer */}
470
+ {isComplete && extractionResult && (
471
  <div className="px-5 py-3 border-t border-slate-100 bg-slate-50/50">
472
  <div className="flex items-center justify-between text-xs">
473
  <div className="flex items-center gap-4">
474
  <div className="flex items-center gap-1.5">
475
+ <div className={cn(
476
+ "h-2 w-2 rounded-full",
477
+ confidence >= 90 ? "bg-emerald-500" : confidence >= 70 ? "bg-amber-500" : "bg-red-500"
478
+ )} />
479
  <span className="text-slate-500">Confidence:</span>
480
+ <span className="font-semibold text-slate-700">
481
+ {confidence > 0 ? `${confidence.toFixed(1)}%` : "N/A"}
482
+ </span>
483
  </div>
484
  <div className="flex items-center gap-1.5">
485
  <span className="text-slate-500">Fields:</span>
486
+ <span className="font-semibold text-slate-700">{fieldsExtracted}</span>
487
  </div>
488
  </div>
489
+ <span className="text-slate-400">
490
+ Processed in {totalTime >= 1000 ? `${(totalTime / 1000).toFixed(1)}s` : `${totalTime}ms`}
491
+ </span>
492
  </div>
493
  </div>
494
  )}