Mango.Nop.Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Extras/ImageTextExtraction.cshtml

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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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>