Seth0330 commited on
Commit
c697880
·
verified ·
1 Parent(s): 525e60d

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

Browse files
frontend/src/components/ocr/ExtractionOutput.jsx ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from "react";
2
+ import { motion, AnimatePresence } from "framer-motion";
3
+ import {
4
+ Code2,
5
+ Copy,
6
+ Check,
7
+ Braces,
8
+ FileCode2,
9
+ FileText,
10
+ Sparkles,
11
+ ChevronDown,
12
+ } from "lucide-react";
13
+ import { Button } from "@/components/ui/button";
14
+ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
15
+ import { cn } from "@/lib/utils";
16
+
17
+ // Mock extracted data
18
+ const mockData = {
19
+ document: {
20
+ type: "Invoice",
21
+ confidence: 0.98,
22
+ },
23
+ vendor: {
24
+ name: "Acme Corporation",
25
+ address: "123 Business Ave, Suite 400",
26
+ city: "San Francisco",
27
+ state: "CA",
28
+ zip: "94102",
29
+ phone: "+1 (555) 123-4567",
30
+ },
31
+ invoice: {
32
+ number: "INV-2024-0847",
33
+ date: "2024-01-15",
34
+ due_date: "2024-02-14",
35
+ po_number: "PO-9823",
36
+ },
37
+ items: [
38
+ { description: "Professional Services", quantity: 40, unit_price: 150.0, total: 6000.0 },
39
+ { description: "Software License", quantity: 5, unit_price: 299.99, total: 1499.95 },
40
+ { description: "Support Package", quantity: 1, unit_price: 500.0, total: 500.0 },
41
+ ],
42
+ totals: {
43
+ subtotal: 7999.95,
44
+ tax_rate: 0.0875,
45
+ tax_amount: 699.99,
46
+ total: 8699.94,
47
+ },
48
+ };
49
+
50
+ const mockXML = `<?xml version="1.0" encoding="UTF-8"?>
51
+ <extraction>
52
+ <document type="Invoice" confidence="0.98"/>
53
+ <vendor>
54
+ <name>Acme Corporation</name>
55
+ <address>123 Business Ave, Suite 400</address>
56
+ <city>San Francisco</city>
57
+ <state>CA</state>
58
+ <zip>94102</zip>
59
+ </vendor>
60
+ <invoice>
61
+ <number>INV-2024-0847</number>
62
+ <date>2024-01-15</date>
63
+ <due_date>2024-02-14</due_date>
64
+ </invoice>
65
+ <items>
66
+ <item>
67
+ <description>Professional Services</description>
68
+ <quantity>40</quantity>
69
+ <total>6000.00</total>
70
+ </item>
71
+ </items>
72
+ <totals>
73
+ <subtotal>7999.95</subtotal>
74
+ <tax>699.99</tax>
75
+ <total>8699.94</total>
76
+ </totals>
77
+ </extraction>`;
78
+
79
+ const mockText = `INVOICE
80
+
81
+ ACME CORPORATION
82
+ 123 Business Ave, Suite 400
83
+ San Francisco, CA 94102
84
+ Phone: +1 (555) 123-4567
85
+
86
+ Invoice Number: INV-2024-0847
87
+ Invoice Date: January 15, 2024
88
+ Due Date: February 14, 2024
89
+ PO Number: PO-9823
90
+
91
+ BILL TO:
92
+ Customer Name
93
+ 456 Client Street
94
+ New York, NY 10001
95
+
96
+ ITEMS:
97
+ ─────────────────────────────────────────────────────────
98
+ Description Qty Unit Price Total
99
+ ─────────────────────────────────────────────────────────
100
+ Professional Services 40 $150.00 $6,000.00
101
+ Software License 5 $299.99 $1,499.95
102
+ Support Package 1 $500.00 $500.00
103
+ ─────────────────────────────────────────────────────────
104
+
105
+ Subtotal: $7,999.95
106
+ Tax (8.75%): $699.99
107
+ ─────────────────────────
108
+ TOTAL: $8,699.94
109
+
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) =>
132
+ prev.includes(section) ? prev.filter((s) => s !== section) : [...prev, section]
133
+ );
134
+ };
135
+
136
+ const renderValue = (value) => {
137
+ if (typeof value === "number") {
138
+ return <span className="text-amber-600">{value}</span>;
139
+ }
140
+ if (typeof value === "string") {
141
+ return <span className="text-emerald-600">"{value}"</span>;
142
+ }
143
+ return String(value);
144
+ };
145
+
146
+ const renderSection = (key, value, level = 0) => {
147
+ const isExpanded = expandedSections.includes(key);
148
+ const isObject = typeof value === "object" && value !== null;
149
+ const isArray = Array.isArray(value);
150
+
151
+ if (!isObject) {
152
+ return (
153
+ <div
154
+ key={key}
155
+ className="flex items-start gap-2 py-1"
156
+ style={{ paddingLeft: level * 16 }}
157
+ >
158
+ <span className="text-violet-500">"{key}"</span>
159
+ <span className="text-slate-400">:</span>
160
+ {renderValue(value)}
161
+ </div>
162
+ );
163
+ }
164
+
165
+ return (
166
+ <div key={key}>
167
+ <button
168
+ onClick={() => toggleSection(key)}
169
+ className="flex items-center gap-2 py-1 hover:bg-slate-50 w-full text-left rounded"
170
+ style={{ paddingLeft: level * 16 }}
171
+ >
172
+ <ChevronDown
173
+ className={cn(
174
+ "h-3 w-3 text-slate-400 transition-transform",
175
+ !isExpanded && "-rotate-90"
176
+ )}
177
+ />
178
+ <span className="text-violet-500">"{key}"</span>
179
+ <span className="text-slate-400">:</span>
180
+ <span className="text-slate-400">{isArray ? "[" : "{"}</span>
181
+ {!isExpanded && (
182
+ <span className="text-slate-300 text-xs">
183
+ {isArray ? `${value.length} items` : `${Object.keys(value).length} fields`}
184
+ </span>
185
+ )}
186
+ </button>
187
+ <AnimatePresence>
188
+ {isExpanded && (
189
+ <motion.div
190
+ initial={{ height: 0, opacity: 0 }}
191
+ animate={{ height: "auto", opacity: 1 }}
192
+ exit={{ height: 0, opacity: 0 }}
193
+ transition={{ duration: 0.2 }}
194
+ className="overflow-hidden"
195
+ >
196
+ {isArray ? (
197
+ value.map((item, idx) => (
198
+ <div key={idx} className="border-l border-slate-100 ml-4">
199
+ {Object.entries(item).map(([k, v]) => renderSection(k, v, level + 2))}
200
+ {idx < value.length - 1 && <div className="h-2" />}
201
+ </div>
202
+ ))
203
+ ) : (
204
+ Object.entries(value).map(([k, v]) => renderSection(k, v, level + 1))
205
+ )}
206
+ <div style={{ paddingLeft: level * 16 }} className="text-slate-400">
207
+ {isArray ? "]" : "}"}
208
+ </div>
209
+ </motion.div>
210
+ )}
211
+ </AnimatePresence>
212
+ </div>
213
+ );
214
+ };
215
+
216
+ return (
217
+ <div className="h-full flex flex-col bg-white rounded-2xl border border-slate-200 overflow-hidden">
218
+ {/* Header */}
219
+ <div className="flex items-center justify-between px-5 py-4 border-b border-slate-100">
220
+ <div className="flex items-center gap-3">
221
+ <div className="h-8 w-8 rounded-lg bg-emerald-50 flex items-center justify-center">
222
+ <Code2 className="h-4 w-4 text-emerald-600" />
223
+ </div>
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>
231
+
232
+ {isComplete && (
233
+ <div className="flex items-center gap-2">
234
+ <Tabs value={activeTab} onValueChange={setActiveTab}>
235
+ <TabsList className="h-8 bg-slate-100 p-0.5">
236
+ <TabsTrigger value="text" className="h-7 text-xs gap-1.5">
237
+ <FileText className="h-3 w-3" />
238
+ Text
239
+ </TabsTrigger>
240
+ <TabsTrigger value="json" className="h-7 text-xs gap-1.5">
241
+ <Braces className="h-3 w-3" />
242
+ JSON
243
+ </TabsTrigger>
244
+ <TabsTrigger value="xml" className="h-7 text-xs gap-1.5">
245
+ <FileCode2 className="h-3 w-3" />
246
+ XML
247
+ </TabsTrigger>
248
+ </TabsList>
249
+ </Tabs>
250
+ <Button
251
+ variant="ghost"
252
+ size="sm"
253
+ onClick={handleCopy}
254
+ className="h-8 text-xs gap-1.5"
255
+ >
256
+ {copied ? (
257
+ <>
258
+ <Check className="h-3 w-3 text-emerald-500" />
259
+ Copied
260
+ </>
261
+ ) : (
262
+ <>
263
+ <Copy className="h-3 w-3" />
264
+ Copy
265
+ </>
266
+ )}
267
+ </Button>
268
+ </div>
269
+ )}
270
+ </div>
271
+
272
+ {/* Output Area */}
273
+ <div className="flex-1 overflow-auto">
274
+ {!hasFile ? (
275
+ <div className="h-full flex items-center justify-center p-6">
276
+ <div className="text-center">
277
+ <div className="h-20 w-20 mx-auto rounded-2xl bg-slate-100 flex items-center justify-center mb-4">
278
+ <Code2 className="h-10 w-10 text-slate-300" />
279
+ </div>
280
+ <p className="text-slate-400 text-sm">Extracted data will appear here</p>
281
+ </div>
282
+ </div>
283
+ ) : isProcessing ? (
284
+ <div className="h-full flex items-center justify-center p-6">
285
+ <div className="text-center">
286
+ <motion.div
287
+ animate={{ rotate: 360 }}
288
+ transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
289
+ className="h-16 w-16 mx-auto rounded-2xl bg-gradient-to-br from-indigo-100 to-violet-100 flex items-center justify-center mb-4"
290
+ >
291
+ <Sparkles className="h-8 w-8 text-indigo-500" />
292
+ </motion.div>
293
+ <p className="text-slate-700 font-medium mb-1">AI is extracting data...</p>
294
+ <p className="text-slate-400 text-sm">Analyzing document structure</p>
295
+
296
+ <div className="mt-6 flex items-center justify-center gap-1">
297
+ {[0, 1, 2].map((i) => (
298
+ <motion.div
299
+ key={i}
300
+ animate={{ scale: [1, 1.2, 1] }}
301
+ transition={{
302
+ duration: 0.6,
303
+ repeat: Infinity,
304
+ delay: i * 0.2,
305
+ }}
306
+ className="h-2 w-2 rounded-full bg-indigo-400"
307
+ />
308
+ ))}
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
+ <>
332
+ {line.split(/(<\/?[\w\s=".-]+>)/g).map((part, j) => {
333
+ if (part.startsWith("</")) {
334
+ return (
335
+ <span key={j} className="text-rose-500">
336
+ {part}
337
+ </span>
338
+ );
339
+ }
340
+ if (part.startsWith("<")) {
341
+ return (
342
+ <span key={j} className="text-indigo-500">
343
+ {part}
344
+ </span>
345
+ );
346
+ }
347
+ return (
348
+ <span key={j} className="text-slate-700">
349
+ {part}
350
+ </span>
351
+ );
352
+ })}
353
+ </>
354
+ ) : (
355
+ line
356
+ )}
357
+ </div>
358
+ ))}
359
+ </pre>
360
+ )}
361
+ </div>
362
+ )}
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
+ )}
384
+ </div>
385
+ );
386
+ }