@tfw.in/structura-lib 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/PRODUCTION_ARCHITECTURE.md +511 -0
  2. package/README.md +379 -0
  3. package/SAVE_FUNCTIONALITY_COMPLETE.md +448 -0
  4. package/dist/cjs/EditableContent.js +150 -0
  5. package/dist/cjs/HtmlViewer.js +587 -0
  6. package/dist/cjs/PdfComponents.js +16 -0
  7. package/dist/cjs/PdfDocumentViewer.js +281 -0
  8. package/dist/cjs/Structura.js +806 -0
  9. package/dist/cjs/Table.js +164 -0
  10. package/dist/cjs/TableCell.js +115 -0
  11. package/dist/cjs/accuracyMetrics.js +39 -0
  12. package/dist/cjs/helpers/preprocessData.js +143 -0
  13. package/dist/cjs/index.js +7 -0
  14. package/dist/cjs/lib/polyfills.js +15 -0
  15. package/dist/cjs/lib/utils.js +10 -0
  16. package/dist/cjs/node_modules/react-icons/fa/index.esm.js +14 -0
  17. package/dist/cjs/node_modules/react-icons/lib/esm/iconBase.js +69 -0
  18. package/dist/cjs/node_modules/react-icons/lib/esm/iconContext.js +15 -0
  19. package/dist/cjs/polyfills.js +19 -0
  20. package/dist/cjs/route.js +102 -0
  21. package/dist/cjs/styles.css +7 -0
  22. package/dist/cjs/styles.css.map +1 -0
  23. package/dist/cjs/ui/badge.js +34 -0
  24. package/dist/cjs/ui/button.js +71 -0
  25. package/dist/cjs/ui/card.js +86 -0
  26. package/dist/cjs/ui/progress.js +45 -0
  27. package/dist/cjs/ui/scroll-area.js +62 -0
  28. package/dist/cjs/ui/tabs.js +60 -0
  29. package/dist/cjs/worker.js +36 -0
  30. package/dist/esm/EditableContent.js +161 -0
  31. package/dist/esm/HtmlViewer.js +640 -0
  32. package/dist/esm/PdfComponents.js +21 -0
  33. package/dist/esm/PdfDocumentViewer.js +294 -0
  34. package/dist/esm/Structura.js +951 -0
  35. package/dist/esm/Table.js +182 -0
  36. package/dist/esm/TableCell.js +122 -0
  37. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +305 -0
  38. package/dist/esm/accuracyMetrics.js +41 -0
  39. package/dist/esm/helpers/preprocessData.js +152 -0
  40. package/dist/esm/index.js +1 -0
  41. package/dist/esm/lib/polyfills.js +13 -0
  42. package/dist/esm/lib/utils.js +8 -0
  43. package/dist/esm/node_modules/react-icons/fa/index.esm.js +11 -0
  44. package/dist/esm/node_modules/react-icons/lib/esm/iconBase.js +66 -0
  45. package/dist/esm/node_modules/react-icons/lib/esm/iconContext.js +12 -0
  46. package/dist/esm/polyfills.js +17 -0
  47. package/dist/esm/route.js +154 -0
  48. package/dist/esm/styles.css +7 -0
  49. package/dist/esm/styles.css.map +1 -0
  50. package/dist/esm/types/EditableContent.d.ts +9 -0
  51. package/dist/esm/types/HtmlViewer.d.ts +10 -0
  52. package/dist/esm/types/PdfComponents.d.ts +35 -0
  53. package/dist/esm/types/PdfDocumentViewer.d.ts +22 -0
  54. package/dist/esm/types/Structura.d.ts +11 -0
  55. package/dist/esm/types/Table.d.ts +12 -0
  56. package/dist/esm/types/TableCell.d.ts +13 -0
  57. package/dist/esm/types/accuracy.d.ts +23 -0
  58. package/dist/esm/types/accuracyMetrics.d.ts +5 -0
  59. package/dist/esm/types/helpers/flattenJSON.d.ts +1 -0
  60. package/dist/esm/types/helpers/hardMerging.d.ts +2 -0
  61. package/dist/esm/types/helpers/index.d.ts +6 -0
  62. package/dist/esm/types/helpers/jsonToHtml.d.ts +40 -0
  63. package/dist/esm/types/helpers/preprocessData.d.ts +3 -0
  64. package/dist/esm/types/helpers/removeMetadata.d.ts +1 -0
  65. package/dist/esm/types/helpers/tableProcessor.d.ts +1 -0
  66. package/dist/esm/types/index.d.ts +3 -0
  67. package/dist/esm/types/lib/polyfills.d.ts +1 -0
  68. package/dist/esm/types/lib/utils.d.ts +2 -0
  69. package/dist/esm/types/polyfills.d.ts +1 -0
  70. package/dist/esm/types/route.d.ts +45 -0
  71. package/dist/esm/types/test-app/src/App.d.ts +4 -0
  72. package/dist/esm/types/test-app/src/main.d.ts +1 -0
  73. package/dist/esm/types/test-app/vite.config.d.ts +2 -0
  74. package/dist/esm/types/types.d.ts +23 -0
  75. package/dist/esm/types/ui/alert.d.ts +8 -0
  76. package/dist/esm/types/ui/badge.d.ts +9 -0
  77. package/dist/esm/types/ui/button.d.ts +11 -0
  78. package/dist/esm/types/ui/card.d.ts +8 -0
  79. package/dist/esm/types/ui/progress.d.ts +6 -0
  80. package/dist/esm/types/ui/scroll-area.d.ts +5 -0
  81. package/dist/esm/types/ui/skeleton.d.ts +2 -0
  82. package/dist/esm/types/ui/tabs.d.ts +7 -0
  83. package/dist/esm/types/worker.d.ts +1 -0
  84. package/dist/esm/ui/badge.js +31 -0
  85. package/dist/esm/ui/button.js +50 -0
  86. package/dist/esm/ui/card.js +67 -0
  87. package/dist/esm/ui/progress.js +26 -0
  88. package/dist/esm/ui/scroll-area.js +45 -0
  89. package/dist/esm/ui/tabs.js +39 -0
  90. package/dist/esm/worker.js +50 -0
  91. package/dist/index.d.ts +38 -0
  92. package/package.json +85 -0
  93. package/server/README.md +203 -0
  94. package/server/db.js +142 -0
  95. package/server/server.js +165 -0
@@ -0,0 +1,806 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var React = require('react');
7
+ var button = require('./ui/button.js');
8
+ var card = require('./ui/card.js');
9
+ var tabs = require('./ui/tabs.js');
10
+ var LucideIcons = require('lucide-react');
11
+ var PdfDocumentViewer = require('./PdfDocumentViewer.js');
12
+ var HtmlViewer = require('./HtmlViewer.js');
13
+ var preprocessData = require('./helpers/preprocessData.js');
14
+ var reactResizablePanels = require('react-resizable-panels');
15
+ var badge = require('./ui/badge.js');
16
+ var progress = require('./ui/progress.js');
17
+ var scrollArea = require('./ui/scroll-area.js');
18
+ var route = require('./route.js');
19
+
20
+ function _interopNamespaceDefault(e) {
21
+ var n = Object.create(null);
22
+ if (e) {
23
+ Object.keys(e).forEach(function (k) {
24
+ if (k !== 'default') {
25
+ var d = Object.getOwnPropertyDescriptor(e, k);
26
+ Object.defineProperty(n, k, d.get ? d : {
27
+ enumerable: true,
28
+ get: function () { return e[k]; }
29
+ });
30
+ }
31
+ });
32
+ }
33
+ n.default = e;
34
+ return Object.freeze(n);
35
+ }
36
+
37
+ var LucideIcons__namespace = /*#__PURE__*/_interopNamespaceDefault(LucideIcons);
38
+
39
+ // Remove component aliases and use direct imports
40
+ // This will preserve the propser types
41
+ function Structura({
42
+ initialPdfPath,
43
+ initialJsonData,
44
+ props
45
+ }) {
46
+ // Log the API key (for development only - remove in production)
47
+ // useEffect(() => {
48
+ // if (props?.APIKey) {
49
+ // console.log("API Key provided:", props.APIKey);
50
+ // }
51
+ // }, [props]);
52
+ // Log the imported SDK items to demonstrate usage
53
+ // console.log("Imported BlockSchema from SDK:", BlockSchema);
54
+ const [numPages, setNumPages] = React.useState(null);
55
+ const [pageNumber, setPageNumber] = React.useState(1);
56
+ const [selectedField, setSelectedField] = React.useState(null);
57
+ const [file, setFile] = React.useState(null);
58
+ const [fileUrl, setFileUrl] = React.useState(initialPdfPath || null);
59
+ const [jsonData, setJsonData] = React.useState(null);
60
+ const [allowedPages, setAllowedPages] = React.useState(null);
61
+ const pdfWrapperRef = React.useRef(null);
62
+ const [flattenedFields, setFlattenedFields] = React.useState([]);
63
+ const [isLoading, setIsLoading] = React.useState(false);
64
+ const [loadingProgress, setLoadingProgress] = React.useState(0);
65
+ const [selectedBboxId, setSelectedBboxId] = React.useState(null);
66
+ const [error, setError] = React.useState(null);
67
+ const [isFileLoaded, setIsFileLoaded] = React.useState(false);
68
+ const [activeTab, setActiveTab] = React.useState("pdf");
69
+ const [backendStatus, setBackendStatus] = React.useState('checking');
70
+ const [isDraggingOver, setIsDraggingOver] = React.useState(false); //NOSONAR
71
+ // Load initial JSON data if provided (for faster iteration without API calls)
72
+ React.useEffect(() => {
73
+ var _a;
74
+ if (initialJsonData) {
75
+ console.log('[Structura] Loading initial JSON data from props');
76
+ setJsonData(initialJsonData);
77
+ const pageCount = ((_a = initialJsonData.children) === null || _a === void 0 ? void 0 : _a.length) || 0;
78
+ setAllowedPages(Array.from({
79
+ length: pageCount
80
+ }, (_, i) => i + 1));
81
+ }
82
+ }, [initialJsonData]);
83
+ React.useEffect(() => {
84
+ const handleDragOver = e => {
85
+ e.preventDefault();
86
+ e.stopPropagation();
87
+ // If PDF viewer is visible and no file is loaded, activate drag indicator
88
+ if (!fileUrl && !isDraggingOver && activeTab === "pdf") {
89
+ setIsDraggingOver(true);
90
+ }
91
+ };
92
+ const handleDragEnter = e => {
93
+ e.preventDefault();
94
+ e.stopPropagation();
95
+ // If PDF viewer is visible and no file is loaded, activate drag indicator
96
+ if (!fileUrl && !isDraggingOver && activeTab === "pdf") {
97
+ setIsDraggingOver(true);
98
+ }
99
+ };
100
+ const handleDragLeave = e => {
101
+ e.preventDefault();
102
+ e.stopPropagation();
103
+ // Only deactivate if we're leaving the document
104
+ if (e.relatedTarget === null) {
105
+ setIsDraggingOver(false);
106
+ }
107
+ };
108
+ const handleDrop = e => {
109
+ e.preventDefault();
110
+ e.stopPropagation();
111
+ setIsDraggingOver(false);
112
+ };
113
+ // Prevent the browser from automatically opening dropped files
114
+ document.addEventListener('dragover', handleDragOver);
115
+ document.addEventListener('dragenter', handleDragEnter);
116
+ document.addEventListener('dragleave', handleDragLeave);
117
+ document.addEventListener('drop', handleDrop);
118
+ return () => {
119
+ document.removeEventListener('dragover', handleDragOver);
120
+ document.removeEventListener('dragenter', handleDragEnter);
121
+ document.removeEventListener('dragleave', handleDragLeave);
122
+ document.removeEventListener('drop', handleDrop);
123
+ };
124
+ }, [fileUrl, isDraggingOver, activeTab]);
125
+ // Add useEffect to handle selected PDF from localStorage
126
+ React.useEffect(() => {
127
+ const handleSelectedPdf = async () => {
128
+ const selectedPdfStr = localStorage.getItem('selectedPdf');
129
+ if (selectedPdfStr) {
130
+ try {
131
+ const selectedPdf = JSON.parse(selectedPdfStr);
132
+ if (selectedPdf.path === 'user-uploaded') {
133
+ // For user-uploaded files, retrieve from sessionStorage
134
+ const fileDataStr = sessionStorage.getItem('pdfFileData');
135
+ if (fileDataStr) {
136
+ try {
137
+ // Restore the file from sessionStorage
138
+ const fileData = JSON.parse(fileDataStr);
139
+ const byteCharacters = atob(fileData.data);
140
+ const byteNumbers = new Array(byteCharacters.length);
141
+ for (let i = 0; i < byteCharacters.length; i++) {
142
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
143
+ }
144
+ const byteArray = new Uint8Array(byteNumbers);
145
+ const blob = new Blob([byteArray], {
146
+ type: 'application/pdf'
147
+ });
148
+ const pdfFile = new File([blob], fileData.name, {
149
+ type: 'application/pdf'
150
+ });
151
+ const newFileUrl = URL.createObjectURL(blob);
152
+ setFile(pdfFile);
153
+ setFileUrl(newFileUrl);
154
+ setIsFileLoaded(true);
155
+ } catch (error) {
156
+ console.error('Error parsing PDF data from sessionStorage:', error);
157
+ setError('Failed to load the PDF file. Please try uploading again.');
158
+ }
159
+ } else {
160
+ setError('PDF data not found. Please upload the file again.');
161
+ }
162
+ } else {
163
+ // For pre-existing PDFs, fetch from the path
164
+ try {
165
+ const response = await fetch(selectedPdf.path);
166
+ const blob = await response.blob();
167
+ const pdfFile = new File([blob], selectedPdf.name, {
168
+ type: 'application/pdf'
169
+ });
170
+ // Set the file and URL
171
+ setFile(pdfFile);
172
+ setFileUrl(URL.createObjectURL(blob));
173
+ setIsFileLoaded(true);
174
+ } catch (error) {
175
+ console.error('Error fetching PDF:', error);
176
+ setError('Failed to load the PDF file. Please try again.');
177
+ }
178
+ }
179
+ // Clear the localStorage
180
+ localStorage.removeItem('selectedPdf');
181
+ } catch (error) {
182
+ console.error('Error loading selected PDF:', error);
183
+ setError('Failed to load selected PDF');
184
+ localStorage.removeItem('selectedPdf');
185
+ }
186
+ } else if (initialPdfPath) {
187
+ // Handle the initialPdfPath if provided
188
+ if (initialPdfPath === 'user-uploaded') {
189
+ // Same logic as above for retrieving from sessionStorage
190
+ const fileDataStr = sessionStorage.getItem('pdfFileData');
191
+ if (fileDataStr) {
192
+ try {
193
+ const fileData = JSON.parse(fileDataStr);
194
+ const byteCharacters = atob(fileData.data);
195
+ const byteNumbers = new Array(byteCharacters.length);
196
+ for (let i = 0; i < byteCharacters.length; i++) {
197
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
198
+ }
199
+ const byteArray = new Uint8Array(byteNumbers);
200
+ const blob = new Blob([byteArray], {
201
+ type: 'application/pdf'
202
+ });
203
+ const pdfFile = new File([blob], fileData.name, {
204
+ type: 'application/pdf'
205
+ });
206
+ const newFileUrl = URL.createObjectURL(blob);
207
+ setFile(pdfFile);
208
+ setFileUrl(newFileUrl);
209
+ setIsFileLoaded(true);
210
+ } catch (error) {
211
+ console.error('Error parsing PDF data from sessionStorage:', error);
212
+ setError('Failed to load the PDF file. Please try uploading again.');
213
+ }
214
+ } else {
215
+ setError('PDF data not found. Please upload the file again.');
216
+ }
217
+ } else {
218
+ // It's a normal path
219
+ try {
220
+ const response = await fetch(initialPdfPath);
221
+ const blob = await response.blob();
222
+ const pdfFile = new File([blob], 'document.pdf', {
223
+ type: 'application/pdf'
224
+ });
225
+ setFile(pdfFile);
226
+ setFileUrl(URL.createObjectURL(blob));
227
+ setIsFileLoaded(true);
228
+ } catch (error) {
229
+ console.error('Error fetching PDF:', error);
230
+ setError(`Failed to load the PDF file from ${initialPdfPath}. Please try again.`);
231
+ }
232
+ }
233
+ }
234
+ };
235
+ handleSelectedPdf();
236
+ }, [initialPdfPath]);
237
+ // Simulate loading progress
238
+ React.useEffect(() => {
239
+ if (isLoading) {
240
+ const interval = setInterval(() => {
241
+ setLoadingProgress(prev => {
242
+ const newProgress = prev + 10;
243
+ if (newProgress >= 100) {
244
+ clearInterval(interval);
245
+ return 100;
246
+ }
247
+ return newProgress;
248
+ });
249
+ }, 300);
250
+ return () => clearInterval(interval);
251
+ } else {
252
+ setLoadingProgress(0);
253
+ }
254
+ }, [isLoading]);
255
+ // Add logging to handleFileUpload
256
+ const handleFileUpload = uploadedFile => {
257
+ // console.log("!!! [Structura] handleFileUpload called with file:", uploadedFile.name);
258
+ // console.log("!!! [Structura] File details:", {
259
+ // name: uploadedFile.name,
260
+ // type: uploadedFile.type,
261
+ // size: uploadedFile.size
262
+ // });
263
+ setFile(uploadedFile);
264
+ setFileUrl(URL.createObjectURL(uploadedFile));
265
+ setJsonData(null);
266
+ setAllowedPages(null);
267
+ setSelectedBboxId(null);
268
+ setIsFileLoaded(false);
269
+ setError(null);
270
+ setActiveTab("pdf");
271
+ // console.log("!!! [Structura] File upload state updated");
272
+ };
273
+ // Add logging to the useEffect that watches isFileLoaded
274
+ React.useEffect(() => {
275
+ // console.log("!!! [Structura] isFileLoaded changed:", isFileLoaded);
276
+ // Skip API call if we already have initialJsonData (cached mode)
277
+ if (isFileLoaded && !initialJsonData) {
278
+ // console.log("!!! [Structura] Calling handleGenerateJSON due to isFileLoaded");
279
+ handleGenerateJSON();
280
+ } else if (isFileLoaded && initialJsonData) {
281
+ console.log('[Structura] Skipping API call - using cached JSON data');
282
+ }
283
+ }, [isFileLoaded, initialJsonData]);
284
+ // Handle removing the PDF and resetting state
285
+ const handleRemovePDF = () => {
286
+ setFile(null);
287
+ setFileUrl(null);
288
+ setJsonData(null);
289
+ setAllowedPages(null);
290
+ setSelectedBboxId(null);
291
+ setIsFileLoaded(false);
292
+ setNumPages(null);
293
+ setPageNumber(1);
294
+ setSelectedField(null);
295
+ setFlattenedFields([]);
296
+ setError(null);
297
+ };
298
+ const handleGenerateJSON = async () => {
299
+ if (!isFileLoaded || !file) return;
300
+ setIsLoading(true);
301
+ setLoadingProgress(0);
302
+ try {
303
+ // Send the PDF file as FormData to the API route
304
+ const formData = new FormData();
305
+ formData.append("file", file);
306
+ // console.log("!!! [Structura] FormData:", formData);
307
+ if (!(props === null || props === void 0 ? void 0 : props.APIKey)) {
308
+ throw new Error("API key is required but not provided");
309
+ }
310
+ const submitResponse = await route.POST(formData, {
311
+ apiKey: props.APIKey,
312
+ baseUrl: props.baseUrl
313
+ });
314
+ console.log("API response status:", submitResponse.status);
315
+ if (submitResponse.status !== 200) {
316
+ const errorData = submitResponse;
317
+ console.error("Submission error details:", errorData);
318
+ if (submitResponse.status === 503) {
319
+ throw new Error(`Backend connection error: ${errorData.details || "Could not connect to backend service"}`);
320
+ } else if (submitResponse.status === 401) {
321
+ throw new Error(`Authentication error: Invalid API key. Please check your API key.`);
322
+ } else {
323
+ throw new Error(`Failed to submit PDF: ${errorData.error || "Unknown error"}`);
324
+ }
325
+ }
326
+ setLoadingProgress(50);
327
+ const requestId = submitResponse.data.request_id;
328
+ // Poll for result every 2 seconds
329
+ let pollCount = 0;
330
+ let resultData = null;
331
+ while (true) {
332
+ const resultResponse = await route.GET(requestId, {
333
+ apiKey: props.APIKey,
334
+ baseUrl: props.baseUrl
335
+ });
336
+ if (resultResponse.data.status === "completed") {
337
+ // Store the response data
338
+ resultData = resultResponse.data;
339
+ // console.log("!!! [Structura] ResultData:", resultData.result);
340
+ break;
341
+ }
342
+ pollCount++;
343
+ // if (pollCount > 60) { // Timeout after 2 minutes
344
+ // throw new Error("Polling timed out. Please try again later.");
345
+ // }
346
+ await new Promise(res => setTimeout(res, 2000));
347
+ }
348
+ // Now validate the data format after polling is complete
349
+ // if (resultData) {
350
+ // // Zod validation
351
+ // const parseResult = DocumentOutputSchema.safeParse(resultData);
352
+ // if (!parseResult.success) {
353
+ // // Handle validation errors
354
+ // console.error("Invalid data format from server:", parseResult.error);
355
+ // setError("Invalid data format from server. Please check console for details.");
356
+ // setIsLoading(false);
357
+ // return;
358
+ // }
359
+ // resultData = parseResult.data;
360
+ // }
361
+ setLoadingProgress(90);
362
+ const processedData = preprocessData.preprocessData(resultData);
363
+ // console.log("!!! [Structura] ProcessedData:", processedData);
364
+ setJsonData(processedData.result);
365
+ setActiveTab("html");
366
+ } catch (error) {
367
+ console.error("Error processing PDF:", error);
368
+ const errorMessage = error instanceof Error ? error.message : "Failed to process PDF";
369
+ setError(`${errorMessage}. ${error.toString().includes("Backend connection") ? "Backend is unavailable." : "Please try again."}`);
370
+ setBackendStatus('offline');
371
+ setJsonData(null);
372
+ } finally {
373
+ setLoadingProgress(100);
374
+ setTimeout(() => {
375
+ setIsLoading(false);
376
+ }, 300);
377
+ }
378
+ };
379
+ // Handle click on HTML element in the right panel
380
+ const handleHtmlNodeClick = nodeId => {
381
+ // console.log(`[PdfHighlighter] HTML Node clicked: ${nodeId}`);
382
+ setSelectedBboxId(nodeId);
383
+ // PDF scrolling will be handled by the useEffect below
384
+ };
385
+ // Handler for when a bbox is clicked in the PDF viewer
386
+ const handlePdfBboxClicked = blockId => {
387
+ // console.log(`[PdfHighlighter] PDF Bbox clicked: ${blockId}`);
388
+ setSelectedBboxId(blockId);
389
+ // HTML scrolling is handled by HtmlViewer's own useEffect
390
+ };
391
+ // Effect to scroll PDF page into view when selectedBboxId changes
392
+ React.useEffect(() => {
393
+ if (!selectedBboxId || !jsonData || !jsonData.children || !pdfWrapperRef.current) {
394
+ return;
395
+ }
396
+ const findPageOfBlock = (blockIdToFind, documentData) => {
397
+ if (!documentData || !documentData.children) return null;
398
+ for (let i = 0; i < documentData.children.length; i++) {
399
+ const pageBlock = documentData.children[i];
400
+ if (pageBlock.block_type === "Page") {
401
+ // Check if the pageBlock itself is the target
402
+ if (pageBlock.id === blockIdToFind) {
403
+ return i + 1; // Page numbers are 1-indexed
404
+ }
405
+ // Recursively search within this page's children
406
+ const blockExistsInPage = currentBlock => {
407
+ if (currentBlock.id === blockIdToFind) return true;
408
+ if (currentBlock.children) {
409
+ for (const child of currentBlock.children) {
410
+ if (blockExistsInPage(child)) return true;
411
+ }
412
+ }
413
+ return false;
414
+ };
415
+ if (blockExistsInPage(pageBlock)) {
416
+ return i + 1; // Page numbers are 1-indexed
417
+ }
418
+ }
419
+ }
420
+ return null;
421
+ };
422
+ const pageNumWithSelectedBlock = findPageOfBlock(selectedBboxId, jsonData);
423
+ if (pageNumWithSelectedBlock !== null) {
424
+ // console.log(`[PdfHighlighter] Selected bbox ${selectedBboxId} found on page ${pageNumWithSelectedBlock}. Scrolling PDF page.`);
425
+ const pageElementId = `pdf-page-container-${pageNumWithSelectedBlock}`;
426
+ const pageElement = document.getElementById(pageElementId); // Check if element exists before timeout
427
+ if (pageElement) {
428
+ // console.log(`[PdfHighlighter] Scheduling scroll for PDF Page element ${pageElementId}`);
429
+ setTimeout(() => {
430
+ const el = document.getElementById(pageElementId); // Re-fetch element in timeout
431
+ if (el) {
432
+ // console.log(`[PdfHighlighter] Timeout: Scrolling to PDF Page element ${el.id} (align to top)`);
433
+ el.scrollIntoView(true); // Align to top, auto behavior
434
+ } else {
435
+ console.warn(`[PdfHighlighter] Timeout: PDF Page element ${pageElementId} not found at time of scroll.`);
436
+ }
437
+ }, 0); // Small delay
438
+ } else {
439
+ console.warn(`[PdfHighlighter] PDF Page element ${pageElementId} not found for scheduling scroll.`);
440
+ }
441
+ } else {
442
+ console.log(`[PdfHighlighter] Selected bbox ${selectedBboxId} not found in any page or jsonData not structured as expected.`);
443
+ }
444
+ }, [selectedBboxId, jsonData, pdfWrapperRef]);
445
+ // Determine which pages to render
446
+ let pagesToRender = [];
447
+ if (numPages) {
448
+ if (allowedPages && allowedPages.length > 0) {
449
+ pagesToRender = allowedPages.filter(p => p >= 1 && p <= numPages);
450
+ } else {
451
+ pagesToRender = Array.from({
452
+ length: numPages
453
+ }, (_, i) => i + 1);
454
+ }
455
+ }
456
+ return jsxRuntime.jsxs("div", {
457
+ className: "h-screen max-h-screen flex flex-col bg-background text-foreground",
458
+ children: [jsxRuntime.jsx("header", {
459
+ className: "border-b border-border bg-card p-4",
460
+ children: jsxRuntime.jsxs("div", {
461
+ className: "container mx-auto flex justify-between items-center",
462
+ children: [jsxRuntime.jsxs("div", {
463
+ className: "flex items-center space-x-2",
464
+ children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
465
+ className: "h-6 w-6 text-primary"
466
+ }), jsxRuntime.jsx("h1", {
467
+ className: "text-2xl mt-2.5 font-bold",
468
+ children: "PLAYGROUND"
469
+ })]
470
+ }), jsxRuntime.jsx("div", {
471
+ className: "flex items-center gap-2",
472
+ children: jsxRuntime.jsx(badge.Badge, {
473
+ variant: "outline",
474
+ className: "px-3 py-1 text-xs",
475
+ children: isFileLoaded ? "Document Loaded" : "No Document"
476
+ })
477
+ })]
478
+ })
479
+ }), jsxRuntime.jsx("div", {
480
+ className: "md:hidden",
481
+ children: jsxRuntime.jsxs(tabs.Tabs, {
482
+ value: activeTab,
483
+ onValueChange: setActiveTab,
484
+ className: "w-full",
485
+ children: [jsxRuntime.jsxs(tabs.TabsList, {
486
+ className: "grid w-full grid-cols-2",
487
+ children: [jsxRuntime.jsxs(tabs.TabsTrigger, {
488
+ value: "pdf",
489
+ className: "flex items-center gap-2",
490
+ children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
491
+ className: "h-4 w-4"
492
+ }), "PDF View"]
493
+ }), jsxRuntime.jsxs(tabs.TabsTrigger, {
494
+ value: "html",
495
+ className: "flex items-center gap-2",
496
+ children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
497
+ className: "h-4 w-4"
498
+ }), "HTML View"]
499
+ })]
500
+ }), jsxRuntime.jsx(tabs.TabsContent, {
501
+ value: "pdf",
502
+ className: "p-0 border-0",
503
+ children: jsxRuntime.jsx("div", {
504
+ ref: pdfWrapperRef,
505
+ className: "h-[calc(100vh-220px)] overflow-auto",
506
+ children: jsxRuntime.jsx(scrollArea.ScrollArea, {
507
+ className: "w-full h-[calc(100vh-220px)]",
508
+ children: fileUrl ? jsxRuntime.jsx(PdfDocumentViewer.default, {
509
+ fileUrl: fileUrl,
510
+ allowedPages: pagesToRender,
511
+ onLoadSuccess: num => {
512
+ // console.log("!!! PDF loaded successfully, numPages:", num);
513
+ setNumPages(num);
514
+ // console.log("!!! Setting isFileLoaded to true");
515
+ setIsFileLoaded(true);
516
+ },
517
+ onFileUpload: handleFileUpload,
518
+ onRemovePDF: handleRemovePDF,
519
+ jsonData: jsonData,
520
+ selectedBboxId: selectedBboxId,
521
+ onPdfBboxClick: handlePdfBboxClicked
522
+ }) : jsxRuntime.jsx("div", {
523
+ className: `min-h-full w-full flex flex-col items-center justify-center p-8 text-center transition-all duration-200 ${isDraggingOver ? 'bg-primary/10 border-2 border-dashed border-primary' : 'bg-muted/50'}`,
524
+ onDragOver: e => {
525
+ e.preventDefault();
526
+ e.stopPropagation();
527
+ setIsDraggingOver(true);
528
+ },
529
+ onDragLeave: e => {
530
+ e.preventDefault();
531
+ e.stopPropagation();
532
+ setIsDraggingOver(false);
533
+ },
534
+ onDrop: e => {
535
+ e.preventDefault();
536
+ e.stopPropagation();
537
+ setIsDraggingOver(false);
538
+ // console.log("!!! File drop event triggered");
539
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
540
+ // If multiple files were dropped, show an error
541
+ if (e.dataTransfer.files.length > 1) {
542
+ // console.log("!!! Multiple files dropped - showing error");
543
+ setError('Please upload only one PDF file at a time');
544
+ return;
545
+ }
546
+ const file = e.dataTransfer.files[0];
547
+ // console.log("!!! Dropped file type:", file.type);
548
+ if (file.type === 'application/pdf') {
549
+ // console.log("!!! Valid PDF file dropped:", file.name);
550
+ handleFileUpload(file);
551
+ } else {
552
+ // console.log("!!! Invalid file type dropped");
553
+ setError(`Invalid file type: ${file.name}. Please upload a PDF file only.`);
554
+ }
555
+ }
556
+ },
557
+ children: jsxRuntime.jsxs("div", {
558
+ className: "max-w-md mx-auto flex flex-col items-center",
559
+ children: [jsxRuntime.jsx("div", {
560
+ className: `w-24 h-24 border-2 border-dashed rounded-full flex items-center justify-center mb-6 transition-all duration-200 ${isDraggingOver ? 'border-primary bg-primary/5 scale-110' : 'border-muted-foreground/50'}`,
561
+ children: jsxRuntime.jsx(LucideIcons__namespace.Upload, {
562
+ className: `h-12 w-12 transition-colors duration-200 ${isDraggingOver ? 'text-primary' : 'text-muted-foreground'}`
563
+ })
564
+ }), jsxRuntime.jsx("h3", {
565
+ className: "text-xl font-medium mb-4",
566
+ children: isDraggingOver ? 'Release to Upload PDF' : 'Drop PDF Here or Click to Upload'
567
+ }), jsxRuntime.jsx("p", {
568
+ className: "text-muted-foreground mb-6",
569
+ children: isDraggingOver ? 'Let go to start processing your document' : 'Drag and drop your PDF document here, or click the button below to browse files'
570
+ }), jsxRuntime.jsxs(button.Button, {
571
+ onClick: () => {
572
+ var _a;
573
+ return (_a = document.getElementById('pdf-upload')) === null || _a === void 0 ? void 0 : _a.click();
574
+ },
575
+ className: "flex items-center gap-2",
576
+ size: "lg",
577
+ children: ["Upload Document ", jsxRuntime.jsx(LucideIcons__namespace.Upload, {
578
+ className: "h-4 w-4 ml-1"
579
+ })]
580
+ }), jsxRuntime.jsx("input", {
581
+ type: "file",
582
+ id: "pdf-upload",
583
+ accept: ".pdf",
584
+ className: "hidden",
585
+ onChange: e => {
586
+ var _a;
587
+ // console.log("!!! File input onChange triggered");
588
+ if ((_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0]) {
589
+ // console.log("!!! File selected from input:", e.target.files[0].name);
590
+ handleFileUpload(e.target.files[0]);
591
+ }
592
+ }
593
+ }), jsxRuntime.jsx("p", {
594
+ className: "text-xs text-muted-foreground mt-4",
595
+ children: "Supported format: PDF only"
596
+ })]
597
+ })
598
+ })
599
+ })
600
+ })
601
+ }), jsxRuntime.jsx(tabs.TabsContent, {
602
+ value: "html",
603
+ className: "p-0 border-0",
604
+ children: jsxRuntime.jsx("div", {
605
+ className: "h-[calc(100vh-220px)]",
606
+ children: jsxRuntime.jsx(HtmlViewer.default, {
607
+ jsonData: jsonData,
608
+ selectedBboxId: selectedBboxId,
609
+ isLoading: isLoading,
610
+ onNodeClick: handleHtmlNodeClick,
611
+ onSave: props.onSave
612
+ })
613
+ })
614
+ })]
615
+ })
616
+ }), jsxRuntime.jsx("div", {
617
+ className: "hidden md:block flex-1 overflow-hidden",
618
+ children: jsxRuntime.jsxs(reactResizablePanels.PanelGroup, {
619
+ direction: "horizontal",
620
+ className: "h-full",
621
+ children: [jsxRuntime.jsx(reactResizablePanels.Panel, {
622
+ minSize: 20,
623
+ defaultSize: 50,
624
+ children: jsxRuntime.jsxs(card.Card, {
625
+ className: "h-full border-0 rounded-none",
626
+ children: [jsxRuntime.jsx(card.CardHeader, {
627
+ className: "py-3 px-4 border-b bg-muted/30",
628
+ children: jsxRuntime.jsxs(card.CardTitle, {
629
+ className: "text-base flex items-center gap-2",
630
+ children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
631
+ className: "h-4 w-4"
632
+ }), "PDF Document", file && jsxRuntime.jsx(badge.Badge, {
633
+ variant: "secondary",
634
+ className: "ml-2",
635
+ children: file.name
636
+ })]
637
+ })
638
+ }), jsxRuntime.jsx(card.CardContent, {
639
+ className: "p-0 flex-1 flex flex-col overflow-hidden",
640
+ children: jsxRuntime.jsx(scrollArea.ScrollArea, {
641
+ className: "w-full h-[calc(100vh-220px)]",
642
+ children: fileUrl ? jsxRuntime.jsx(PdfDocumentViewer.default, {
643
+ fileUrl: fileUrl,
644
+ allowedPages: pagesToRender,
645
+ onLoadSuccess: num => {
646
+ // console.log("!!! PDF loaded successfully, numPages:", num);
647
+ setNumPages(num);
648
+ // console.log("!!! Setting isFileLoaded to true");
649
+ setIsFileLoaded(true);
650
+ },
651
+ onFileUpload: handleFileUpload,
652
+ onRemovePDF: handleRemovePDF,
653
+ jsonData: jsonData,
654
+ selectedBboxId: selectedBboxId,
655
+ onPdfBboxClick: handlePdfBboxClicked
656
+ }) : jsxRuntime.jsx("div", {
657
+ className: `min-h-full w-full flex flex-col items-center justify-center p-8 text-center transition-all duration-200 ${isDraggingOver ? 'bg-primary/10 border-2 border-dashed border-primary' : 'bg-muted/50'}`,
658
+ onDragOver: e => {
659
+ e.preventDefault();
660
+ e.stopPropagation();
661
+ setIsDraggingOver(true);
662
+ },
663
+ onDragLeave: e => {
664
+ e.preventDefault();
665
+ e.stopPropagation();
666
+ setIsDraggingOver(false);
667
+ },
668
+ onDrop: e => {
669
+ e.preventDefault();
670
+ e.stopPropagation();
671
+ setIsDraggingOver(false);
672
+ // console.log("!!! File drop event triggered");
673
+ if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
674
+ // If multiple files were dropped, show an error
675
+ if (e.dataTransfer.files.length > 1) {
676
+ // console.log("!!! Multiple files dropped - showing error");
677
+ setError('Please upload only one PDF file at a time');
678
+ return;
679
+ }
680
+ const file = e.dataTransfer.files[0];
681
+ // console.log("!!! Dropped file type:", file.type);
682
+ if (file.type === 'application/pdf') {
683
+ // console.log("!!! Valid PDF file dropped:", file.name);
684
+ handleFileUpload(file);
685
+ } else {
686
+ // console.log("!!! Invalid file type dropped");
687
+ setError(`Invalid file type: ${file.name}. Please upload a PDF file only.`);
688
+ }
689
+ }
690
+ },
691
+ children: jsxRuntime.jsxs("div", {
692
+ className: "max-w-md mx-auto flex flex-col items-center",
693
+ children: [jsxRuntime.jsx("div", {
694
+ className: `w-24 h-24 border-2 border-dashed rounded-full flex items-center justify-center mb-6 transition-all duration-200 ${isDraggingOver ? 'border-primary bg-primary/5 scale-110' : 'border-muted-foreground/50'}`,
695
+ children: jsxRuntime.jsx(LucideIcons__namespace.Upload, {
696
+ className: `h-12 w-12 transition-colors duration-200 ${isDraggingOver ? 'text-primary' : 'text-muted-foreground'}`
697
+ })
698
+ }), jsxRuntime.jsx("h3", {
699
+ className: "text-xl font-medium mb-4",
700
+ children: isDraggingOver ? 'Release to Upload PDF' : 'Drop PDF Here or Click to Upload'
701
+ }), jsxRuntime.jsx("p", {
702
+ className: "text-muted-foreground mb-6",
703
+ children: isDraggingOver ? 'Let go to start processing your document' : 'Drag and drop your PDF document here, or click the button below to browse files'
704
+ }), jsxRuntime.jsxs(button.Button, {
705
+ onClick: () => {
706
+ var _a;
707
+ return (_a = document.getElementById('pdf-upload')) === null || _a === void 0 ? void 0 : _a.click();
708
+ },
709
+ className: "flex items-center gap-2",
710
+ size: "lg",
711
+ children: ["Upload Document ", jsxRuntime.jsx(LucideIcons__namespace.Upload, {
712
+ className: "h-4 w-4 ml-1"
713
+ })]
714
+ }), jsxRuntime.jsx("input", {
715
+ type: "file",
716
+ id: "pdf-upload",
717
+ accept: ".pdf",
718
+ className: "hidden",
719
+ onChange: e => {
720
+ var _a;
721
+ // console.log("!!! File input onChange triggered");
722
+ if ((_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0]) {
723
+ // console.log("!!! File selected from input:", e.target.files[0].name);
724
+ handleFileUpload(e.target.files[0]);
725
+ }
726
+ }
727
+ }), jsxRuntime.jsx("p", {
728
+ className: "text-xs text-muted-foreground mt-4",
729
+ children: "Supported format: PDF only"
730
+ })]
731
+ })
732
+ })
733
+ })
734
+ })]
735
+ })
736
+ }), jsxRuntime.jsx(reactResizablePanels.PanelResizeHandle, {
737
+ className: "w-1.5 bg-black hover:bg-primary/20 transition-colors cursor-col-resize"
738
+ }), jsxRuntime.jsx(reactResizablePanels.Panel, {
739
+ minSize: 20,
740
+ defaultSize: 50,
741
+ children: jsxRuntime.jsxs(card.Card, {
742
+ className: "h-full border-0 rounded-none",
743
+ children: [jsxRuntime.jsx(card.CardHeader, {
744
+ className: "py-3 px-4 border-b bg-muted/30",
745
+ children: jsxRuntime.jsxs(card.CardTitle, {
746
+ className: "text-base flex items-center gap-2",
747
+ children: [jsxRuntime.jsx(LucideIcons__namespace.FileText, {
748
+ className: "h-4 w-4"
749
+ }), "HTML Structure"]
750
+ })
751
+ }), jsxRuntime.jsx(card.CardContent, {
752
+ className: "p-0 h-[calc(100%-56px)] relative overflow-hidden",
753
+ children: jsxRuntime.jsx("div", {
754
+ className: "absolute inset-0",
755
+ children: jsonData ? jsxRuntime.jsx(HtmlViewer.default, {
756
+ jsonData: jsonData,
757
+ selectedBboxId: selectedBboxId,
758
+ isLoading: isLoading,
759
+ onNodeClick: handleHtmlNodeClick,
760
+ onSave: props.onSave
761
+ }) : jsxRuntime.jsx("div", {
762
+ className: "flex flex-col items-center justify-center h-full p-8 text-center",
763
+ children: jsxRuntime.jsxs("div", {
764
+ className: "bg-muted/50 p-8 rounded-lg flex flex-col items-center",
765
+ children: [jsxRuntime.jsx(LucideIcons__namespace.Upload, {
766
+ className: "h-12 w-12 text-muted-foreground mb-4"
767
+ }), jsxRuntime.jsx("h3", {
768
+ className: "text-lg font-medium mb-2",
769
+ children: "No Data Available"
770
+ }), jsxRuntime.jsx("p", {
771
+ className: "text-muted-foreground mb-4",
772
+ children: "First upload a PDF document, then generate the HTML structure"
773
+ })]
774
+ })
775
+ })
776
+ })
777
+ })]
778
+ })
779
+ })]
780
+ })
781
+ }), isLoading && jsxRuntime.jsx("div", {
782
+ className: "fixed bottom-0 left-0 right-0 bg-background border-t border-border p-4 z-50",
783
+ children: jsxRuntime.jsx("div", {
784
+ className: "container mx-auto",
785
+ children: jsxRuntime.jsxs("div", {
786
+ className: "flex flex-col gap-2",
787
+ children: [jsxRuntime.jsxs("div", {
788
+ className: "flex items-center justify-between",
789
+ children: [jsxRuntime.jsx("span", {
790
+ className: "text-sm font-medium",
791
+ children: "Processing document..."
792
+ }), jsxRuntime.jsxs("span", {
793
+ className: "text-sm text-muted-foreground",
794
+ children: [loadingProgress, "%"]
795
+ })]
796
+ }), jsxRuntime.jsx(progress.Progress, {
797
+ value: loadingProgress,
798
+ className: "h-2"
799
+ })]
800
+ })
801
+ })
802
+ })]
803
+ });
804
+ }
805
+
806
+ exports.default = Structura;