350 lines
16 KiB
Plaintext
350 lines
16 KiB
Plaintext
@{
|
|
Layout = "_ConfigurePlugin";
|
|
}
|
|
|
|
@await Component.InvokeAsync("StoreScopeConfiguration")
|
|
|
|
<form asp-action="ExtractTextFromImage" asp-controller="FileManager" method="post">
|
|
@Html.AntiForgeryToken()
|
|
</form>
|
|
|
|
<div class="card card-default">
|
|
<div class="card-body">
|
|
<h4>Image & PDF Text Extraction (OCR)</h4>
|
|
<p>Upload an image or PDF to extract text using OpenAI Vision API.</p>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-md-9">
|
|
<div class="custom-file">
|
|
<input type="file" class="custom-file-input" id="imageFile" accept="image/*,.pdf,application/pdf">
|
|
<label class="custom-file-label" for="imageFile">Choose image or PDF file...</label>
|
|
</div>
|
|
<small class="form-text text-muted">Supported formats: JPG, PNG, GIF, WebP, PDF</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-md-9">
|
|
<label for="customPrompt">Custom Prompt (Optional)</label>
|
|
<input type="text" class="form-control" id="customPrompt" placeholder="Leave empty for default: 'Olvasd ki a szöveget és add vissza szépen strukturálva.'">
|
|
<small class="form-text text-muted">Customize how the AI should extract and format the text</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-md-9">
|
|
<button type="button" id="uploadButton" class="btn btn-primary" disabled>
|
|
<i class="fas fa-upload"></i> Extract Text
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-md-9">
|
|
<div id="responseMessage" class="alert" style="display:none;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row" id="filePreviewSection" style="display:none;">
|
|
<div class="col-md-9">
|
|
<h5>File Preview:</h5>
|
|
<div id="imagePreviewContainer" style="display:none;">
|
|
<img id="imagePreview" src="" alt="Preview" style="max-width: 100%; max-height: 400px; border: 1px solid #ddd; padding: 5px;">
|
|
</div>
|
|
<div id="pdfPreviewContainer" style="display:none;">
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-file-pdf"></i> PDF file selected. The first page will be converted to image for text extraction.
|
|
</div>
|
|
<embed id="pdfPreview" type="application/pdf" style="width: 100%; height: 500px; border: 1px solid #ddd;">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shipping Document Section -->
|
|
<div class="form-group row" id="shippingDocumentSection" style="display:none;">
|
|
<div class="col-md-12">
|
|
<h4 class="mt-4"><i class="fas fa-shipping-fast"></i> Shipping Document Details</h4>
|
|
|
|
<!-- Document Info Card -->
|
|
<div class="card card-primary">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="fas fa-file-alt"></i> Document Information</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<strong>Document ID:</strong>
|
|
<p id="documentIdNumber" class="text-muted">-</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<strong>Partner ID:</strong>
|
|
<p id="partnerId" class="text-muted">-</p>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<strong>Total Pallets:</strong>
|
|
<p id="totalPallets" class="text-muted">-</p>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<strong>PDF Filename:</strong>
|
|
<p id="pdfFileName" class="text-muted">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shipping Items Table -->
|
|
<div class="card card-info mt-3">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="fas fa-boxes"></i> Shipping Items (<span id="itemCount">0</span>)</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-striped" id="shippingItemsTable">
|
|
<thead class="thead-dark">
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Name</th>
|
|
<th>Hungarian Name</th>
|
|
<th>Name on Document</th>
|
|
<th>Product ID</th>
|
|
<th>Pallets</th>
|
|
<th>Quantity</th>
|
|
<th>Net Weight (kg)</th>
|
|
<th>Gross Weight (kg)</th>
|
|
<th>Measurable</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="shippingItemsBody">
|
|
<!-- Items will be populated here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Extracted Text Card (Collapsible) -->
|
|
<div class="card card-secondary mt-3">
|
|
<div class="card-header" data-toggle="collapse" data-target="#extractedTextCollapse" style="cursor: pointer;">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-file-alt"></i> Raw Extracted Text
|
|
<i class="fas fa-chevron-down float-right"></i>
|
|
</h5>
|
|
</div>
|
|
<div id="extractedTextCollapse" class="collapse">
|
|
<div class="card-body">
|
|
<pre id="extractedText" style="white-space: pre-wrap; word-wrap: break-word; max-height: 400px; overflow-y: auto;"></pre>
|
|
<button type="button" id="copyButton" class="btn btn-secondary mt-2">
|
|
<i class="fas fa-copy"></i> Copy to Clipboard
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const imageFileInput = document.getElementById('imageFile');
|
|
const customPromptInput = document.getElementById('customPrompt');
|
|
const uploadButton = document.getElementById('uploadButton');
|
|
const responseMessage = document.getElementById('responseMessage');
|
|
const filePreviewSection = document.getElementById('filePreviewSection');
|
|
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
|
|
const pdfPreviewContainer = document.getElementById('pdfPreviewContainer');
|
|
const imagePreview = document.getElementById('imagePreview');
|
|
const pdfPreview = document.getElementById('pdfPreview');
|
|
const shippingDocumentSection = document.getElementById('shippingDocumentSection');
|
|
const extractedText = document.getElementById('extractedText');
|
|
const copyButton = document.getElementById('copyButton');
|
|
const fileLabel = document.querySelector('.custom-file-label');
|
|
|
|
let selectedFile = null;
|
|
|
|
// Update file label and enable upload button when file is selected
|
|
imageFileInput.addEventListener('change', (event) => {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
selectedFile = file;
|
|
fileLabel.textContent = file.name;
|
|
uploadButton.disabled = false;
|
|
|
|
// Check file type and show appropriate preview
|
|
const isPdf = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf');
|
|
|
|
if (isPdf) {
|
|
// Show PDF preview
|
|
imagePreviewContainer.style.display = 'none';
|
|
pdfPreviewContainer.style.display = 'block';
|
|
|
|
const fileUrl = URL.createObjectURL(file);
|
|
pdfPreview.src = fileUrl;
|
|
|
|
filePreviewSection.style.display = 'block';
|
|
} else {
|
|
// Show image preview
|
|
pdfPreviewContainer.style.display = 'none';
|
|
imagePreviewContainer.style.display = 'block';
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
imagePreview.src = e.target.result;
|
|
filePreviewSection.style.display = 'block';
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
} else {
|
|
selectedFile = null;
|
|
fileLabel.textContent = 'Choose image or PDF file...';
|
|
uploadButton.disabled = true;
|
|
filePreviewSection.style.display = 'none';
|
|
}
|
|
shippingDocumentSection.style.display = 'none';
|
|
});
|
|
|
|
uploadButton.addEventListener('click', async () => {
|
|
if (!selectedFile) {
|
|
showMessage('Please select a file first!', 'warning');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('imageFile', selectedFile);
|
|
|
|
const customPrompt = customPromptInput.value.trim();
|
|
if (customPrompt) {
|
|
formData.append('customPrompt', customPrompt);
|
|
}
|
|
|
|
try {
|
|
uploadButton.disabled = true;
|
|
const isPdf = selectedFile.type === 'application/pdf' || selectedFile.name.toLowerCase().endsWith('.pdf');
|
|
uploadButton.innerHTML = isPdf
|
|
? '<i class="fas fa-spinner fa-spin"></i> Converting PDF & Extracting...'
|
|
: '<i class="fas fa-spinner fa-spin"></i> Extracting...';
|
|
|
|
// Get the antiforgery token
|
|
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
|
|
|
|
const response = await fetch('@Url.Action("ExtractTextFromImage", "FileManager")', {
|
|
method: 'POST',
|
|
headers: {
|
|
'RequestVerificationToken': token
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
const message = result.wasConverted
|
|
? 'PDF converted and text extracted successfully!'
|
|
: 'Text extracted successfully!';
|
|
showMessage(message, 'success');
|
|
|
|
// Display shipping document data
|
|
if (result.shippingDocument) {
|
|
displayShippingDocument(result.shippingDocument);
|
|
document.getElementById('shippingDocumentSection').style.display = 'block';
|
|
}
|
|
} else {
|
|
showMessage('Error: ' + (result.message || 'Failed to extract text'), 'danger');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error extracting text:', error);
|
|
showMessage('Error: Failed to communicate with server', 'danger');
|
|
} finally {
|
|
uploadButton.disabled = false;
|
|
uploadButton.innerHTML = '<i class="fas fa-upload"></i> Extract Text';
|
|
}
|
|
});
|
|
|
|
copyButton.addEventListener('click', () => {
|
|
const textToCopy = extractedText.textContent;
|
|
navigator.clipboard.writeText(textToCopy).then(() => {
|
|
const originalText = copyButton.innerHTML;
|
|
copyButton.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
|
copyButton.classList.remove('btn-secondary');
|
|
copyButton.classList.add('btn-success');
|
|
|
|
setTimeout(() => {
|
|
copyButton.innerHTML = originalText;
|
|
copyButton.classList.remove('btn-success');
|
|
copyButton.classList.add('btn-secondary');
|
|
}, 2000);
|
|
}).catch(err => {
|
|
console.error('Failed to copy text:', err);
|
|
showMessage('Failed to copy to clipboard', 'warning');
|
|
});
|
|
});
|
|
|
|
function displayShippingDocument(shippingDoc) {
|
|
// Populate document information
|
|
document.getElementById('documentIdNumber').textContent = shippingDoc.documentIdNumber || 'N/A';
|
|
document.getElementById('partnerId').textContent = shippingDoc.partnerId || 'N/A';
|
|
document.getElementById('totalPallets').textContent = shippingDoc.totalPallets || '0';
|
|
document.getElementById('pdfFileName').textContent = shippingDoc.pdfFileName || 'N/A';
|
|
|
|
// Populate extracted text (collapsible section)
|
|
extractedText.textContent = shippingDoc.extractedText || 'No text extracted';
|
|
|
|
// Populate shipping items table
|
|
const tbody = document.getElementById('shippingItemsBody');
|
|
tbody.innerHTML = ''; // Clear existing rows
|
|
|
|
const items = shippingDoc.shippingItems || [];
|
|
document.getElementById('itemCount').textContent = items.length;
|
|
|
|
if (items.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="10" class="text-center text-muted">No shipping items found</td></tr>';
|
|
} else {
|
|
items.forEach((item, index) => {
|
|
const row = document.createElement('tr');
|
|
|
|
// Add status class based on whether product is matched
|
|
if (!item.productId) {
|
|
row.classList.add('table-warning');
|
|
}
|
|
|
|
row.innerHTML = `
|
|
<td>${index + 1}</td>
|
|
<td>${escapeHtml(item.name || '-')}</td>
|
|
<td>${escapeHtml(item.hungarianName || '-')}</td>
|
|
<td>${escapeHtml(item.nameOnDocument || '-')}</td>
|
|
<td>${item.productId ? `<span class="badge badge-success">${item.productId}</span>` : '<span class="badge badge-warning">Not Matched</span>'}</td>
|
|
<td>${item.palletsOnDocument || '0'}</td>
|
|
<td>${item.quantityOnDocument || '0'}</td>
|
|
<td>${item.netWeightOnDocument ? item.netWeightOnDocument.toFixed(2) : '0.00'}</td>
|
|
<td>${item.grossWeightOnDocument ? item.grossWeightOnDocument.toFixed(2) : '0.00'}</td>
|
|
<td>${item.isMeasurable ? '<span class="badge badge-info">Yes</span>' : '<span class="badge badge-secondary">No</span>'}</td>
|
|
`;
|
|
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text.replace(/[&<>"']/g, m => map[m]);
|
|
}
|
|
|
|
function showMessage(message, type) {
|
|
responseMessage.textContent = message;
|
|
responseMessage.className = 'alert alert-' + type;
|
|
responseMessage.style.display = 'block';
|
|
|
|
setTimeout(() => {
|
|
responseMessage.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
</script> |