Compare commits
No commits in common. "2cd76a58a5e52fc2d9ba551d945982a05bfb6ca8" and "02f7c768cb26494347ff9ecb32f713bf52ddad67" have entirely different histories.
2cd76a58a5
...
02f7c768cb
|
|
@ -226,7 +226,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
PaymentStatusIds = paymentStatuses,
|
PaymentStatusIds = paymentStatuses,
|
||||||
ShippingStatusIds = shippingStatuses,
|
ShippingStatusIds = shippingStatuses,
|
||||||
AvailablePageSizes = "20,50,100,500",
|
AvailablePageSizes = "20,50,100,500",
|
||||||
SortColumnDirection = "desc",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/List.cshtml", model);
|
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Order/List.cshtml", model);
|
||||||
|
|
|
||||||
|
|
@ -1,177 +1,12 @@
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Nop.Core.Domain.Catalog;
|
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Services;
|
|
||||||
using Nop.Services.Catalog;
|
|
||||||
using Nop.Services.Security;
|
|
||||||
using Nop.Web.Framework;
|
|
||||||
using Nop.Web.Framework.Controllers;
|
|
||||||
using Nop.Web.Framework.Mvc.Filters;
|
|
||||||
using PDFtoImage;
|
|
||||||
using SkiaSharp;
|
|
||||||
|
|
||||||
|
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
|
||||||
namespace Nop.Plugin.Misc.FruitBank.Controllers
|
|
||||||
{
|
{
|
||||||
[AuthorizeAdmin]
|
public class FileManagerController : Controller
|
||||||
[Area(AreaNames.ADMIN)]
|
|
||||||
[AutoValidateAntiforgeryToken]
|
|
||||||
public class FileManagerController : BasePluginController
|
|
||||||
{
|
{
|
||||||
private readonly IPermissionService _permissionService;
|
public IActionResult BindingToFileSystem()
|
||||||
private readonly OpenAIApiService _aiApiService;
|
|
||||||
private readonly IProductService _productService;
|
|
||||||
private readonly FruitBankDbContext _dbContext;
|
|
||||||
private readonly PdfToImageService _pdfToImageService;
|
|
||||||
|
|
||||||
public FileManagerController(
|
|
||||||
IPermissionService permissionService,
|
|
||||||
OpenAIApiService aiApiService,
|
|
||||||
IProductService productService,
|
|
||||||
FruitBankDbContext fruitBankDbContext,
|
|
||||||
PdfToImageService pdfToImageService)
|
|
||||||
{
|
{
|
||||||
_permissionService = permissionService;
|
return View();
|
||||||
_aiApiService = aiApiService;
|
|
||||||
_productService = productService;
|
|
||||||
_dbContext = fruitBankDbContext;
|
|
||||||
_pdfToImageService = pdfToImageService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Display the image text extraction page
|
|
||||||
/// </summary>
|
|
||||||
public async Task<IActionResult> ImageTextExtraction()
|
|
||||||
{
|
|
||||||
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
|
||||||
return AccessDeniedView();
|
|
||||||
|
|
||||||
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Extras/ImageTextExtraction.cshtml");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Endpoint to extract text from uploaded image
|
|
||||||
/// </summary>
|
|
||||||
[HttpPost]
|
|
||||||
public async Task<IActionResult> ExtractTextFromImage(IFormFile imageFile, string customPrompt = null)
|
|
||||||
{
|
|
||||||
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
|
||||||
return Json(new { success = false, message = "Access denied" });
|
|
||||||
|
|
||||||
if (imageFile == null || imageFile.Length == 0)
|
|
||||||
{
|
|
||||||
return Json(new { success = false, message = "No file received" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate file type - now including PDF
|
|
||||||
var extension = Path.GetExtension(imageFile.FileName).ToLowerInvariant();
|
|
||||||
if (extension != ".jpg" && extension != ".jpeg" && extension != ".png" &&
|
|
||||||
extension != ".gif" && extension != ".webp" && extension != ".pdf")
|
|
||||||
{
|
|
||||||
return Json(new { success = false, message = "Invalid file type. Please upload JPG, PNG, GIF, WebP, or PDF." });
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Define the uploads folder
|
|
||||||
var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "ocr");
|
|
||||||
|
|
||||||
// Create directory if it doesn't exist
|
|
||||||
if (!Directory.Exists(uploadsFolder))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(uploadsFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
string processedFilePath;
|
|
||||||
string processedFileName;
|
|
||||||
|
|
||||||
// Handle PDF conversion
|
|
||||||
if (extension == ".pdf")
|
|
||||||
{
|
|
||||||
// Save the PDF temporarily
|
|
||||||
var tempPdfFileName = $"temp_pdf_{DateTime.Now:yyyyMMdd_HHmmss}.pdf";
|
|
||||||
var tempPdfPath = Path.Combine(uploadsFolder, tempPdfFileName);
|
|
||||||
|
|
||||||
using (var stream = new FileStream(tempPdfPath, FileMode.Create))
|
|
||||||
{
|
|
||||||
await imageFile.CopyToAsync(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert PDF to JPG using our service
|
|
||||||
var convertedImages = await _pdfToImageService.ConvertPdfToJpgAsync(tempPdfPath, uploadsFolder);
|
|
||||||
|
|
||||||
if (convertedImages == null || convertedImages.Count == 0)
|
|
||||||
{
|
|
||||||
// Clean up temp PDF
|
|
||||||
if (System.IO.File.Exists(tempPdfPath))
|
|
||||||
System.IO.File.Delete(tempPdfPath);
|
|
||||||
|
|
||||||
return Json(new { success = false, message = "Failed to convert PDF or PDF is empty" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the first page
|
|
||||||
processedFilePath = convertedImages[0];
|
|
||||||
processedFileName = Path.GetFileName(processedFilePath);
|
|
||||||
|
|
||||||
// Clean up temp PDF
|
|
||||||
if (System.IO.File.Exists(tempPdfPath))
|
|
||||||
System.IO.File.Delete(tempPdfPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Handle regular image files
|
|
||||||
processedFileName = $"ocr_image_{DateTime.Now:yyyyMMdd_HHmmss}{extension}";
|
|
||||||
processedFilePath = Path.Combine(uploadsFolder, processedFileName);
|
|
||||||
|
|
||||||
using (var stream = new FileStream(processedFilePath, FileMode.Create))
|
|
||||||
{
|
|
||||||
await imageFile.CopyToAsync(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract text from the processed image using OpenAI Vision API
|
|
||||||
string extractedText;
|
|
||||||
using (var imageStream = new FileStream(processedFilePath, FileMode.Open, FileAccess.Read))
|
|
||||||
{
|
|
||||||
extractedText = await _aiApiService.ExtractTextFromImageAsync(
|
|
||||||
imageStream,
|
|
||||||
processedFileName,
|
|
||||||
customPrompt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(extractedText))
|
|
||||||
{
|
|
||||||
return Json(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = "Failed to extract text. The API may have returned an empty response."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
message = extension == ".pdf"
|
|
||||||
? "PDF converted and text extracted successfully"
|
|
||||||
: "Text extracted successfully",
|
|
||||||
extractedText = extractedText,
|
|
||||||
fileName = processedFileName,
|
|
||||||
filePath = processedFilePath,
|
|
||||||
fileSize = imageFile.Length,
|
|
||||||
wasConverted = extension == ".pdf"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Error in ExtractTextFromImage: {ex}");
|
|
||||||
return Json(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = $"Error processing file: {ex.Message}"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
|
||||||
using Nop.Plugin.Misc.FruitBankPlugin.Services;
|
|
||||||
using Nop.Services.Security;
|
|
||||||
using Nop.Web.Framework;
|
|
||||||
using Nop.Web.Framework.Controllers;
|
|
||||||
using Nop.Web.Framework.Mvc.Filters;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Nop.Plugin.Misc.FruitBank.Controllers
|
|
||||||
{
|
|
||||||
[AuthorizeAdmin]
|
|
||||||
[Area(AreaNames.ADMIN)]
|
|
||||||
[AutoValidateAntiforgeryToken]
|
|
||||||
public class FruitBankAudioController : BasePluginController
|
|
||||||
{
|
|
||||||
private readonly IPermissionService _permissionService;
|
|
||||||
private readonly OpenAIApiService _aiApiService;
|
|
||||||
private readonly FruitBankDbContext _dbContext;
|
|
||||||
|
|
||||||
public FruitBankAudioController(
|
|
||||||
IPermissionService permissionService,
|
|
||||||
OpenAIApiService aiApiService,
|
|
||||||
FruitBankDbContext fruitBankDbContext)
|
|
||||||
{
|
|
||||||
_permissionService = permissionService;
|
|
||||||
_aiApiService = aiApiService;
|
|
||||||
_dbContext = fruitBankDbContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Display the voice recorder page
|
|
||||||
/// </summary>
|
|
||||||
public async Task<IActionResult> VoiceRecorder()
|
|
||||||
{
|
|
||||||
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
|
||||||
return AccessDeniedView();
|
|
||||||
|
|
||||||
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Extras/VoiceRecorder.cshtml");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Endpoint to receive voice recording
|
|
||||||
/// </summary>
|
|
||||||
[HttpPost]
|
|
||||||
public async Task<IActionResult> ReceiveVoiceRecording(IFormFile audioFile)
|
|
||||||
{
|
|
||||||
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
|
||||||
return Json(new { success = false, message = "Access denied" });
|
|
||||||
|
|
||||||
if (audioFile == null || audioFile.Length == 0)
|
|
||||||
{
|
|
||||||
return Json(new { success = false, message = "No audio file received" });
|
|
||||||
}
|
|
||||||
|
|
||||||
string productDataSummary = string.Empty;
|
|
||||||
string orderDataSummary = string.Empty;
|
|
||||||
|
|
||||||
var products = await _dbContext.Products.GetAll().ToListAsync();
|
|
||||||
foreach (var product in products)
|
|
||||||
{
|
|
||||||
//let's prepare basic product and stock information for AI analysis
|
|
||||||
if (product == null) continue;
|
|
||||||
|
|
||||||
var productName = product.Name;
|
|
||||||
var stockQuantity = product.StockQuantity;
|
|
||||||
|
|
||||||
productDataSummary += $"Product: {productName}, Stock Quantity: {stockQuantity}\n";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var orders = await _dbContext.OrderDtos.GetAll(true).ToListAsync();
|
|
||||||
|
|
||||||
foreach (var order in orders)
|
|
||||||
{
|
|
||||||
//let's prepare basic order information for AI analysis
|
|
||||||
if (order == null) continue;
|
|
||||||
var orderId = order.Id;
|
|
||||||
var customerName = order.Customer.Company;
|
|
||||||
var totalAmount = order.OrderTotal;
|
|
||||||
var dateofReceipt = order.DateOfReceipt;
|
|
||||||
var isMeasurable = order.IsMeasurable;
|
|
||||||
var itemsInOrder = order.OrderItemDtos.Count;
|
|
||||||
orderDataSummary += $"Order ID: {orderId}, Customer: {customerName}, Total Amount: {totalAmount}, Date of Receipt: {dateofReceipt}, Is Measurable: {isMeasurable}, Items in Order: {itemsInOrder}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Console.WriteLine("Product Data Summary: " + productDataSummary);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Generate a unique filename
|
|
||||||
var fileName = $"voice_recording_{DateTime.Now:yyyyMMdd_HHmmss}.webm";
|
|
||||||
|
|
||||||
// Define the path where you want to save the file
|
|
||||||
var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "voice");
|
|
||||||
|
|
||||||
// Create directory if it doesn't exist
|
|
||||||
if (!Directory.Exists(uploadsFolder))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(uploadsFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
var filePath = Path.Combine(uploadsFolder, fileName);
|
|
||||||
|
|
||||||
// Save the file locally
|
|
||||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
|
||||||
{
|
|
||||||
await audioFile.CopyToAsync(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transcribe the audio using OpenAI Whisper API
|
|
||||||
string transcribedText;
|
|
||||||
using (var audioStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
|
||||||
{
|
|
||||||
transcribedText = await _aiApiService.TranscribeAudioAsync(audioStream, fileName, "en"); // or "hu" for Hungarian
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(transcribedText))
|
|
||||||
{
|
|
||||||
return Json(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = "Failed to transcribe audio"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
string SystemMessage = "You are an assistant that helps with analyzing data for Fruitbank, a fruit trading company based in Hungary. " +
|
|
||||||
"Provide insights and suggestions based on the provided data." +
|
|
||||||
$"Products information: {productDataSummary}" +
|
|
||||||
$"Orders information: {orderDataSummary}";
|
|
||||||
|
|
||||||
|
|
||||||
var AIResponse = await _aiApiService.GetSimpleResponseAsync(SystemMessage, $"Transcribed Text: {transcribedText}");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return Json(new
|
|
||||||
{
|
|
||||||
success = true,
|
|
||||||
message = $"Audio transcribed successfully",
|
|
||||||
transcription = AIResponse,
|
|
||||||
filePath = filePath,
|
|
||||||
fileSize = audioFile.Length
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Json(new
|
|
||||||
{
|
|
||||||
success = false,
|
|
||||||
message = $"Error processing audio: {ex.Message}"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models
|
||||||
public record EditShippingModel : BaseNopModel
|
public record EditShippingModel : BaseNopModel
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public string LicencePlate { get; set; }
|
public string LicencePlate { get; set; }
|
||||||
|
|
||||||
|
|
@ -61,4 +61,4 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models
|
||||||
public List<int> DocumentIds { get; set; } = new();
|
public List<int> DocumentIds { get; set; } = new();
|
||||||
public string Operation { get; set; } // "delete", "activate", "deactivate"
|
public string Operation { get; set; } // "delete", "activate", "deactivate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
@{
|
|
||||||
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>
|
|
||||||
|
|
||||||
<div class="form-group row" id="extractedTextSection" style="display:none;">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<h5>Extracted Text:</h5>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<pre id="extractedText" style="white-space: pre-wrap; word-wrap: break-word; max-height: 500px; overflow-y: auto;"></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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 extractedTextSection = document.getElementById('extractedTextSection');
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
extractedTextSection.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 extracted text
|
|
||||||
if (result.extractedText) {
|
|
||||||
extractedText.textContent = result.extractedText;
|
|
||||||
extractedTextSection.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 showMessage(message, type) {
|
|
||||||
responseMessage.textContent = message;
|
|
||||||
responseMessage.className = 'alert alert-' + type;
|
|
||||||
responseMessage.style.display = 'block';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
responseMessage.style.display = 'none';
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,176 +0,0 @@
|
||||||
@{
|
|
||||||
Layout = "_ConfigurePlugin";
|
|
||||||
}
|
|
||||||
|
|
||||||
@await Component.InvokeAsync("StoreScopeConfiguration")
|
|
||||||
|
|
||||||
<form asp-action="ReceiveVoiceRecording" asp-controller="FruitBankAdmin" method="post">
|
|
||||||
@Html.AntiForgeryToken()
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="card card-default">
|
|
||||||
<div class="card-body">
|
|
||||||
<h4>Voice Recorder</h4>
|
|
||||||
<p>Click the button below to start recording your voice message.</p>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<button type="button" id="recordButton" class="btn btn-primary">
|
|
||||||
<i class="fas fa-microphone"></i> Start Recording
|
|
||||||
</button>
|
|
||||||
<button type="button" id="stopButton" class="btn btn-danger" style="display:none;">
|
|
||||||
<i class="fas fa-stop"></i> Stop Recording
|
|
||||||
</button>
|
|
||||||
<span id="recordingStatus" style="margin-left: 15px; font-weight: bold;"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" id="audioPlaybackSection" style="display:none;">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<audio id="audioPlayback" controls style="width: 100%;"></audio>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row" id="sendSection" style="display:none;">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<button type="button" id="sendButton" class="btn btn-success">
|
|
||||||
<i class="fas fa-paper-plane"></i> Send to API
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-md-9">
|
|
||||||
<div id="responseMessage" class="alert" style="display:none;"></div>
|
|
||||||
<div id="transcriptionResult" style="display:none; margin-top: 15px;">
|
|
||||||
<h5>Transcription:</h5>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<p id="transcriptionText" style="white-space: pre-wrap;"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let mediaRecorder;
|
|
||||||
let audioChunks = [];
|
|
||||||
let audioBlob;
|
|
||||||
|
|
||||||
const recordButton = document.getElementById('recordButton');
|
|
||||||
const stopButton = document.getElementById('stopButton');
|
|
||||||
const sendButton = document.getElementById('sendButton');
|
|
||||||
const recordingStatus = document.getElementById('recordingStatus');
|
|
||||||
const audioPlayback = document.getElementById('audioPlayback');
|
|
||||||
const audioPlaybackSection = document.getElementById('audioPlaybackSection');
|
|
||||||
const sendSection = document.getElementById('sendSection');
|
|
||||||
const responseMessage = document.getElementById('responseMessage');
|
|
||||||
const transcriptionResult = document.getElementById('transcriptionResult');
|
|
||||||
const transcriptionText = document.getElementById('transcriptionText');
|
|
||||||
|
|
||||||
recordButton.addEventListener('click', async () => {
|
|
||||||
try {
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
||||||
|
|
||||||
mediaRecorder = new MediaRecorder(stream);
|
|
||||||
audioChunks = [];
|
|
||||||
|
|
||||||
mediaRecorder.ondataavailable = (event) => {
|
|
||||||
audioChunks.push(event.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
mediaRecorder.onstop = () => {
|
|
||||||
audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
|
||||||
const audioUrl = URL.createObjectURL(audioBlob);
|
|
||||||
audioPlayback.src = audioUrl;
|
|
||||||
audioPlaybackSection.style.display = 'block';
|
|
||||||
sendSection.style.display = 'block';
|
|
||||||
|
|
||||||
// Stop all tracks to release the microphone
|
|
||||||
stream.getTracks().forEach(track => track.stop());
|
|
||||||
};
|
|
||||||
|
|
||||||
mediaRecorder.start();
|
|
||||||
recordButton.style.display = 'none';
|
|
||||||
stopButton.style.display = 'inline-block';
|
|
||||||
recordingStatus.textContent = 'Recording...';
|
|
||||||
recordingStatus.style.color = 'red';
|
|
||||||
audioPlaybackSection.style.display = 'none';
|
|
||||||
sendSection.style.display = 'none';
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error accessing microphone:', error);
|
|
||||||
showMessage('Error: Could not access microphone. Please check permissions.', 'danger');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stopButton.addEventListener('click', () => {
|
|
||||||
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
|
|
||||||
mediaRecorder.stop();
|
|
||||||
recordButton.style.display = 'inline-block';
|
|
||||||
stopButton.style.display = 'none';
|
|
||||||
recordingStatus.textContent = 'Recording stopped';
|
|
||||||
recordingStatus.style.color = 'green';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sendButton.addEventListener('click', async () => {
|
|
||||||
if (!audioBlob) {
|
|
||||||
showMessage('No audio recorded yet!', 'warning');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('audioFile', audioBlob, 'recording.webm');
|
|
||||||
|
|
||||||
try {
|
|
||||||
sendButton.disabled = true;
|
|
||||||
sendButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
|
||||||
|
|
||||||
// Get the antiforgery token
|
|
||||||
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
|
|
||||||
|
|
||||||
const response = await fetch('@Url.Action("ReceiveVoiceRecording", "FruitBankAudio")', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'RequestVerificationToken': token
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (response.ok && result.success) {
|
|
||||||
showMessage('Audio transcribed successfully!', 'success');
|
|
||||||
|
|
||||||
// Display transcription
|
|
||||||
if (result.transcription) {
|
|
||||||
transcriptionText.textContent = result.transcription;
|
|
||||||
transcriptionResult.style.display = 'block';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showMessage('Error: ' + (result.message || 'Failed to transcribe audio'), 'danger');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending audio:', error);
|
|
||||||
showMessage('Error: Failed to send audio to server', 'danger');
|
|
||||||
} finally {
|
|
||||||
sendButton.disabled = false;
|
|
||||||
sendButton.innerHTML = '<i class="fas fa-paper-plane"></i> Send to API';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showMessage(message, type) {
|
|
||||||
responseMessage.textContent = message;
|
|
||||||
responseMessage.className = 'alert alert-' + type;
|
|
||||||
responseMessage.style.display = 'block';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
responseMessage.style.display = 'none';
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
@ -345,7 +345,7 @@
|
||||||
@{
|
@{
|
||||||
var gridModel = new DataTablesModel
|
var gridModel = new DataTablesModel
|
||||||
{
|
{
|
||||||
Name = "orders-grid",
|
Name = "orders-grid",
|
||||||
UrlRead = new DataUrl("OrderList", "CustomOrder", null),
|
UrlRead = new DataUrl("OrderList", "CustomOrder", null),
|
||||||
SearchButtonId = "search-orders",
|
SearchButtonId = "search-orders",
|
||||||
Ordering = true,
|
Ordering = true,
|
||||||
|
|
@ -400,19 +400,10 @@
|
||||||
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.InnvoiceTechId))
|
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.InnvoiceTechId))
|
||||||
{
|
{
|
||||||
Title = "Innvoiceba beküldve",
|
Title = "Innvoiceba beküldve",
|
||||||
Width = "100",
|
Width = "150",
|
||||||
Render = new RenderCustom("renderColumnInnvoiceTechId"),
|
Render = new RenderCustom("renderColumnInnvoiceTechId"),
|
||||||
ClassName = NopColumnClassDefaults.CenterAll
|
ClassName = NopColumnClassDefaults.CenterAll
|
||||||
});
|
});
|
||||||
|
|
||||||
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.IsAllOrderItemAvgWeightValid))
|
|
||||||
{
|
|
||||||
Title = "Súlyeltérés",
|
|
||||||
Width = "100",
|
|
||||||
Render = new RenderCustom("renderColumnAverageWeightError"),
|
|
||||||
ClassName = NopColumnClassDefaults.CenterAll
|
|
||||||
});
|
|
||||||
|
|
||||||
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.IsMeasurable))
|
gridModel.ColumnCollection.Add(new ColumnProperty(nameof(OrderModelExtended.IsMeasurable))
|
||||||
{
|
{
|
||||||
Title = T($"FruitBank.{nameof(OrderModelExtended.IsMeasurable)}").Text,
|
Title = T($"FruitBank.{nameof(OrderModelExtended.IsMeasurable)}").Text,
|
||||||
|
|
@ -551,14 +542,7 @@
|
||||||
if(data != null) {
|
if(data != null) {
|
||||||
return '<span class="badge badge-success" disabled>Igen</span>';
|
return '<span class="badge badge-success" disabled>Igen</span>';
|
||||||
}
|
}
|
||||||
return '<span class="badge badge-danger" disabled>Nem</span>';
|
return '<span class="badge badge-warning" disabled>Nem</span>';
|
||||||
}
|
|
||||||
|
|
||||||
function renderColumnAverageWeightError(data, type, row, meta) {
|
|
||||||
if(data) {
|
|
||||||
return '<span class="badge badge-success" disabled>OK</span>';
|
|
||||||
}
|
|
||||||
return '<span class="badge badge-danger" disabled>!!!</span>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderColumnIsMeasurable(data, type, row, meta) {
|
function renderColumnIsMeasurable(data, type, row, meta) {
|
||||||
|
|
|
||||||
|
|
@ -159,12 +159,7 @@
|
||||||
<th>
|
<th>
|
||||||
Mérés állapota
|
Mérés állapota
|
||||||
</th>
|
</th>
|
||||||
<th>
|
|
||||||
Súlyeltérés
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Súlyeltérés mértéke
|
|
||||||
</th>
|
|
||||||
@* <th>
|
@* <th>
|
||||||
@T("Admin.Orders.Products.Discount")
|
@T("Admin.Orders.Products.Discount")
|
||||||
</th> *@
|
</th> *@
|
||||||
|
|
@ -341,24 +336,6 @@
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td style="width: 80px;" class="text-center">
|
|
||||||
@if (!item.AverageWeightIsValid)
|
|
||||||
{
|
|
||||||
<span class="badge badge-danger" disabled>!!!</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="badge badge-success" disabled>OK</span>
|
|
||||||
}
|
|
||||||
<input type="hidden" name="pvAverageWeightIsValid@(item.Id)" id="pvAverageWeightIsValid@(item.Id)" value="@(item.AverageWeightIsValid.ToString())" disabled />
|
|
||||||
</td>
|
|
||||||
<td style="width: 80px;" class="text-center">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="">Eltérés: @item.AverageWeightDifference (KG), Mért átlag: @item.AverageWeight (KG/rekesz)</span>
|
|
||||||
|
|
||||||
<input type="hidden" name="pvAverageWeightDifference@(item.Id)" id="pvAverageWeightDifference@(item.Id)" value="@(item.AverageWeightDifference.ToString())" disabled />
|
|
||||||
</td>
|
|
||||||
@* <td style="width: 15%;" class="text-center">
|
@* <td style="width: 15%;" class="text-center">
|
||||||
@if (Model.AllowCustomersToSelectTaxDisplayType)
|
@if (Model.AllowCustomersToSelectTaxDisplayType)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -285,10 +285,6 @@
|
||||||
{
|
{
|
||||||
Title = "Súly(kg)"//T("Admin.Catalog.Products.Fields.NetWeight").Text
|
Title = "Súly(kg)"//T("Admin.Catalog.Products.Fields.NetWeight").Text
|
||||||
},
|
},
|
||||||
new ColumnProperty(nameof(ProductModelExtended.AverageWeight))
|
|
||||||
{
|
|
||||||
Title = "Átlagsúly (kg)"//T("Admin.Catalog.Products.Fields.AverageWeight").Text
|
|
||||||
},
|
|
||||||
new ColumnProperty(nameof(ProductModelExtended.Tare))
|
new ColumnProperty(nameof(ProductModelExtended.Tare))
|
||||||
{
|
{
|
||||||
Title = "Tára(kg)"//T("Admin.Catalog.Products.Fields.Tare").Text
|
Title = "Tára(kg)"//T("Admin.Catalog.Products.Fields.Tare").Text
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Components
|
||||||
|
|
||||||
model.Tare = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, double>(model.ProductId, nameof(ITare.Tare));
|
model.Tare = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, double>(model.ProductId, nameof(ITare.Tare));
|
||||||
model.IncomingQuantity = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, int>(model.ProductId, nameof(IIncomingQuantity.IncomingQuantity));
|
model.IncomingQuantity = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, int>(model.ProductId, nameof(IIncomingQuantity.IncomingQuantity));
|
||||||
model.AverageWeight = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, double>(model.ProductId, nameof(IProductDto.AverageWeight));
|
|
||||||
model.AverageWeightTreshold = await _fruitBankAttributeService.GetGenericAttributeValueAsync<Product, double>(model.ProductId, nameof(IProductDto.AverageWeightTreshold));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return View("~/Plugins/Misc.FruitBankPlugin/Views/ProductAttributes.cshtml", model);
|
return View("~/Plugins/Misc.FruitBankPlugin/Views/ProductAttributes.cshtml", model);
|
||||||
|
|
|
||||||
|
|
@ -129,20 +129,6 @@ public class FruitBankEventConsumer :
|
||||||
if (productDto == null || productDto.Tare != tare)
|
if (productDto == null || productDto.Tare != tare)
|
||||||
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(ITare.Tare), tare);
|
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(ITare.Tare), tare);
|
||||||
|
|
||||||
//AverageWeight
|
|
||||||
var averageWeight = double.Round(CommonHelper.To<double>(form[nameof(IProductDto.AverageWeight)].ToString()), 1);
|
|
||||||
if (averageWeight < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (averageWeight < 0); productId: {product.Id}; averageWeight: {averageWeight}");
|
|
||||||
|
|
||||||
if (productDto == null || productDto.AverageWeight != averageWeight)
|
|
||||||
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(IProductDto.AverageWeight), averageWeight);
|
|
||||||
|
|
||||||
//AverageWeightTreshold
|
|
||||||
var averageWeightTreshold = double.Round(CommonHelper.To<double>(form[nameof(IProductDto.AverageWeightTreshold)].ToString()), 1);
|
|
||||||
if (averageWeightTreshold < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (averageWeightTreshold < 0); productId: {product.Id}; averageWeight: {averageWeightTreshold}");
|
|
||||||
|
|
||||||
if (productDto == null || productDto.AverageWeightTreshold != averageWeightTreshold)
|
|
||||||
await _fruitBankAttributeService.InsertOrUpdateGenericAttributeAsync<Product, double>(product.Id, nameof(IProductDto.AverageWeightTreshold), averageWeightTreshold);
|
|
||||||
|
|
||||||
//IncomingQuantity
|
//IncomingQuantity
|
||||||
var incomingQuantity = CommonHelper.To<int>(form[nameof(IIncomingQuantity.IncomingQuantity)].ToString());
|
var incomingQuantity = CommonHelper.To<int>(form[nameof(IIncomingQuantity.IncomingQuantity)].ToString());
|
||||||
if (incomingQuantity < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (incomingQuantity < 0); productId: {product.Id}; incomingQuantity: {incomingQuantity}");
|
if (incomingQuantity < 0) throw new Exception($"FruitBankEventConsumer->SaveProductCustomAttributesAsync(); (incomingQuantity < 0); productId: {product.Id}; incomingQuantity: {incomingQuantity}");
|
||||||
|
|
|
||||||
|
|
@ -182,8 +182,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
|
||||||
orderModelExtended.IsMeasurable = orderDto.IsMeasurable;
|
orderModelExtended.IsMeasurable = orderDto.IsMeasurable;
|
||||||
orderModelExtended.DateOfReceipt = orderDto.DateOfReceipt;
|
orderModelExtended.DateOfReceipt = orderDto.DateOfReceipt;
|
||||||
orderModelExtended.OrderTotal = !orderDto.IsComplete && orderDto.IsMeasurable ? "kalkuláció alatt..." : orderModelExtended.OrderTotal;
|
orderModelExtended.OrderTotal = !orderDto.IsComplete && orderDto.IsMeasurable ? "kalkuláció alatt..." : orderModelExtended.OrderTotal;
|
||||||
orderModelExtended.IsAllOrderItemAvgWeightValid = orderDto.IsAllOrderItemAvgWeightValid;
|
|
||||||
|
|
||||||
|
|
||||||
//var fullName = $"{orderDto.Customer.FirstName}_{orderDto.Customer.LastName}".Trim();
|
//var fullName = $"{orderDto.Customer.FirstName}_{orderDto.Customer.LastName}".Trim();
|
||||||
orderModelExtended.CustomerCompany = $"{orderDto.Customer.Company} {orderDto.Customer.FirstName}_{orderDto.Customer.LastName}";
|
orderModelExtended.CustomerCompany = $"{orderDto.Customer.Company} {orderDto.Customer.FirstName}_{orderDto.Customer.LastName}";
|
||||||
|
|
@ -228,9 +226,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Factories
|
||||||
orderItemModelExtended.ProductStockQuantity = orderItemDto.ProductDto!.StockQuantity;
|
orderItemModelExtended.ProductStockQuantity = orderItemDto.ProductDto!.StockQuantity;
|
||||||
orderItemModelExtended.ProductIncomingQuantity = orderItemDto.ProductDto.IncomingQuantity;
|
orderItemModelExtended.ProductIncomingQuantity = orderItemDto.ProductDto.IncomingQuantity;
|
||||||
orderItemModelExtended.ProductAvailableQuantity = orderItemDto.ProductDto.AvailableQuantity;
|
orderItemModelExtended.ProductAvailableQuantity = orderItemDto.ProductDto.AvailableQuantity;
|
||||||
orderItemModelExtended.AverageWeight = orderItemDto.AverageWeight;
|
|
||||||
orderItemModelExtended.AverageWeightIsValid = orderItemDto.AverageWeightIsValid;
|
|
||||||
orderItemModelExtended.AverageWeightDifference = orderItemDto.AverageWeightDifference;
|
|
||||||
|
|
||||||
orderItemModelExtended.SubTotalInclTax = orderItemDto.IsMeasurable && !orderItemDto.IsAudited ? "kalkuláció alatt..." : orderItemModelExtended.SubTotalInclTax;
|
orderItemModelExtended.SubTotalInclTax = orderItemDto.IsMeasurable && !orderItemDto.IsAudited ? "kalkuláció alatt..." : orderItemModelExtended.SubTotalInclTax;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,6 @@ public class CustomProductModelFactory : MgProductModelFactory<ProductListModelE
|
||||||
productModelExtended.AvailableQuantity = productDto.AvailableQuantity;
|
productModelExtended.AvailableQuantity = productDto.AvailableQuantity;
|
||||||
|
|
||||||
productModelExtended.StockQuantityStr = productModelExtended.StockQuantity.ToString();
|
productModelExtended.StockQuantityStr = productModelExtended.StockQuantity.ToString();
|
||||||
|
|
||||||
productModelExtended.AverageWeight = productDto.AverageWeight;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return productListModelExtended;
|
return productListModelExtended;
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,6 @@ public class PluginNopStartup : INopStartup
|
||||||
services.AddScoped<OpenAIApiService>();
|
services.AddScoped<OpenAIApiService>();
|
||||||
//services.AddScoped<IAIAPIService, OpenAIApiService>();
|
//services.AddScoped<IAIAPIService, OpenAIApiService>();
|
||||||
services.AddScoped<AICalculationService>();
|
services.AddScoped<AICalculationService>();
|
||||||
services.AddScoped<PdfToImageService>();
|
|
||||||
|
|
||||||
services.AddControllersWithViews(options =>
|
services.AddControllersWithViews(options =>
|
||||||
{
|
{
|
||||||
options.Filters.AddService<PendingMeasurementCheckoutFilter>();
|
options.Filters.AddService<PendingMeasurementCheckoutFilter>();
|
||||||
|
|
|
||||||
|
|
@ -171,16 +171,6 @@ public class RouteProvider : IRouteProvider
|
||||||
name: "Plugin.FruitBank.AppDownload.Download",
|
name: "Plugin.FruitBank.AppDownload.Download",
|
||||||
pattern: "Admin/AppDownload/Download/{version}/{fileName}",
|
pattern: "Admin/AppDownload/Download/{version}/{fileName}",
|
||||||
defaults: new { controller = "AppDownload", action = "Download", area = AreaNames.ADMIN });
|
defaults: new { controller = "AppDownload", action = "Download", area = AreaNames.ADMIN });
|
||||||
|
|
||||||
endpointRouteBuilder.MapControllerRoute(
|
|
||||||
name: "Plugin.FruitBank.Admin.FruitBankAudio",
|
|
||||||
pattern: "Admin/VoiceRecorder",
|
|
||||||
defaults: new { controller = "FruitBankAudio", action = "VoiceRecorder", area = AreaNames.ADMIN });
|
|
||||||
|
|
||||||
endpointRouteBuilder.MapControllerRoute(
|
|
||||||
name: "Plugin.FruitBank.Admin.ExtractText",
|
|
||||||
pattern: "Admin/ExtractText",
|
|
||||||
defaults: new { controller = "FileManager", action = "ImageTextExtraction", area = AreaNames.ADMIN });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
|
||||||
public int ProductStockQuantity { get; set; }
|
public int ProductStockQuantity { get; set; }
|
||||||
public int ProductIncomingQuantity { get; set; }
|
public int ProductIncomingQuantity { get; set; }
|
||||||
public int ProductAvailableQuantity { get; set; }
|
public int ProductAvailableQuantity { get; set; }
|
||||||
public double AverageWeight { get; set; }
|
|
||||||
public bool AverageWeightIsValid { get; set; }
|
|
||||||
public double AverageWeightDifference { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial record OrderModelExtended : MgOrderModelExtended, IOrderModelExtended
|
public partial record OrderModelExtended : MgOrderModelExtended, IOrderModelExtended
|
||||||
|
|
@ -34,8 +28,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models.Orders
|
||||||
|
|
||||||
public string InnvoiceTechId { get; set; }
|
public string InnvoiceTechId { get; set; }
|
||||||
|
|
||||||
public bool IsAllOrderItemAvgWeightValid { get; set; }
|
|
||||||
|
|
||||||
public IList<OrderItemModelExtended> ItemExtendeds { get; set; }
|
public IList<OrderItemModelExtended> ItemExtendeds { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,5 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models
|
||||||
|
|
||||||
[NopResourceDisplayName("Plugins.YourCompany.ProductAttributes.Fields.Tare")]
|
[NopResourceDisplayName("Plugins.YourCompany.ProductAttributes.Fields.Tare")]
|
||||||
public double Tare { get; set; }
|
public double Tare { get; set; }
|
||||||
|
|
||||||
[NopResourceDisplayName("Plugins.YourCompany.ProductAttributes.Fields.AverageWeight")]
|
|
||||||
public double AverageWeight { get; set; }
|
|
||||||
|
|
||||||
[NopResourceDisplayName("Plugins.YourCompany.ProductAttributes.Fields.AverageWeightTreshold")]
|
|
||||||
public double AverageWeightTreshold { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,8 +13,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Models.Products
|
||||||
public int IncomingQuantity { get; set; }
|
public int IncomingQuantity { get; set; }
|
||||||
|
|
||||||
public int AvailableQuantity { get; set; }
|
public int AvailableQuantity { get; set; }
|
||||||
|
|
||||||
public double AverageWeight { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,18 +28,13 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.Json" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.Json" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
||||||
|
|
||||||
<!--<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />-->
|
<!--<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />-->
|
||||||
<PackageReference Include="PdfPig" Version="0.1.11" />
|
<PackageReference Include="PdfPig" Version="0.1.11" />
|
||||||
<PackageReference Include="PdfPig.Rendering.Skia" Version="0.1.11.5" />
|
<PackageReference Include="PdfPig.Rendering.Skia" Version="0.1.11.5" />
|
||||||
<PackageReference Include="SendGrid" Version="9.29.3" />
|
<PackageReference Include="SendGrid" Version="9.29.3" />
|
||||||
<PackageReference Include="System.Memory.Data" Version="9.0.10" />
|
<PackageReference Include="System.Memory.Data" Version="9.0.10" />
|
||||||
<PackageReference Include="Tesseract" Version="5.2.0" />
|
<PackageReference Include="Tesseract" Version="5.2.0" />
|
||||||
<PackageReference Include="TesseractOCR" Version="5.5.1" />
|
<PackageReference Include="TesseractOCR" Version="5.5.1" />
|
||||||
<PackageReference Include="PDFtoImage" Version="4.0.2" />
|
|
||||||
<PackageReference Include="PDFtoImage" Version="4.0.2">
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -176,12 +171,6 @@
|
||||||
<None Update="Areas\Admin\Views\AppDownload\Index.cshtml">
|
<None Update="Areas\Admin\Views\AppDownload\Index.cshtml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Areas\Admin\Views\Extras\ImageTextExtraction.cshtml">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Areas\Admin\Views\Extras\VoiceRecorder.cshtml">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="Areas\Admin\Views\Order\FileUploadGridComponent.cshtml">
|
<None Update="Areas\Admin\Views\Order\FileUploadGridComponent.cshtml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
@ -666,10 +655,4 @@
|
||||||
<Target Name="NopTarget" AfterTargets="Build">
|
<Target Name="NopTarget" AfterTargets="Build">
|
||||||
<MSBuild Projects="@(ClearPluginAssemblies)" Properties="PluginPath=$(OutDir)" Targets="NopClear" />
|
<MSBuild Projects="@(ClearPluginAssemblies)" Properties="PluginPath=$(OutDir)" Targets="NopClear" />
|
||||||
</Target>
|
</Target>
|
||||||
<Target Name="CopyPdfiumDll" AfterTargets="Build">
|
|
||||||
<ItemGroup>
|
|
||||||
<PdfiumFiles Include="$(NuGetPackageRoot)pdfium.binaries\**\pdfium.dll" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Copy SourceFiles="@(PdfiumFiles)" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true" />
|
|
||||||
</Target>
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -20,7 +20,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
private const string OpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
|
private const string OpenAiEndpoint = "https://api.openai.com/v1/chat/completions";
|
||||||
private const string OpenAiImageEndpoint = "https://api.openai.com/v1/images/generations";
|
private const string OpenAiImageEndpoint = "https://api.openai.com/v1/images/generations";
|
||||||
private const string OpenAiFileEndpoint = "https://api.openai.com/v1/files";
|
private const string OpenAiFileEndpoint = "https://api.openai.com/v1/files";
|
||||||
private const string OpenAiAudioEndpoint = "https://api.openai.com/v1/audio/transcriptions";
|
|
||||||
private const string BaseUrl = "https://api.openai.com/v1";
|
private const string BaseUrl = "https://api.openai.com/v1";
|
||||||
|
|
||||||
private string? _assistantId;
|
private string? _assistantId;
|
||||||
|
|
@ -44,79 +43,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
_onError = onErrorCallback;
|
_onError = onErrorCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region === AUDIO TRANSCRIPTION ===
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transcribe audio file to text using OpenAI Whisper API
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="audioStream">The audio file stream</param>
|
|
||||||
/// <param name="fileName">The original filename (used to determine format)</param>
|
|
||||||
/// <param name="language">Optional language code (e.g., "en", "hu"). If null, auto-detects.</param>
|
|
||||||
/// <returns>The transcribed text</returns>
|
|
||||||
public async Task<string?> TranscribeAudioAsync(Stream audioStream, string fileName, string? language = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var form = new MultipartFormDataContent();
|
|
||||||
|
|
||||||
// Add the audio file
|
|
||||||
var audioContent = new StreamContent(audioStream);
|
|
||||||
|
|
||||||
// Determine content type based on file extension
|
|
||||||
var extension = Path.GetExtension(fileName).ToLowerInvariant();
|
|
||||||
audioContent.Headers.ContentType = extension switch
|
|
||||||
{
|
|
||||||
".mp3" => new MediaTypeHeaderValue("audio/mpeg"),
|
|
||||||
".mp4" => new MediaTypeHeaderValue("audio/mp4"),
|
|
||||||
".mpeg" => new MediaTypeHeaderValue("audio/mpeg"),
|
|
||||||
".mpga" => new MediaTypeHeaderValue("audio/mpeg"),
|
|
||||||
".m4a" => new MediaTypeHeaderValue("audio/m4a"),
|
|
||||||
".wav" => new MediaTypeHeaderValue("audio/wav"),
|
|
||||||
".webm" => new MediaTypeHeaderValue("audio/webm"),
|
|
||||||
_ => new MediaTypeHeaderValue("application/octet-stream")
|
|
||||||
};
|
|
||||||
|
|
||||||
form.Add(audioContent, "file", fileName);
|
|
||||||
|
|
||||||
// Add model
|
|
||||||
form.Add(new StringContent("whisper-1"), "model");
|
|
||||||
|
|
||||||
// Add language if specified
|
|
||||||
if (!string.IsNullOrEmpty(language))
|
|
||||||
{
|
|
||||||
form.Add(new StringContent(language), "language");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional: Add response format (json is default, can also be text, srt, verbose_json, or vtt)
|
|
||||||
form.Add(new StringContent("json"), "response_format");
|
|
||||||
|
|
||||||
var response = await _httpClient.PostAsync(OpenAiAudioEndpoint, form);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var error = await response.Content.ReadAsStringAsync();
|
|
||||||
Console.WriteLine($"Audio transcription failed: {error}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var contentStream = await response.Content.ReadAsStreamAsync();
|
|
||||||
using var json = await JsonDocument.ParseAsync(contentStream);
|
|
||||||
|
|
||||||
var transcription = json.RootElement.GetProperty("text").GetString();
|
|
||||||
|
|
||||||
Console.WriteLine($"Audio transcription successful. Length: {transcription?.Length ?? 0} characters");
|
|
||||||
|
|
||||||
return transcription;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Error transcribing audio: {ex}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region === CHAT (TEXT INPUT) ===
|
#region === CHAT (TEXT INPUT) ===
|
||||||
|
|
||||||
public async Task<string?> GetSimpleResponseAsync(string systemMessage, string userMessage, string? assistantMessage = null)
|
public async Task<string?> GetSimpleResponseAsync(string systemMessage, string userMessage, string? assistantMessage = null)
|
||||||
|
|
@ -391,24 +317,24 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
Console.WriteLine("Cleaning up all existing vector stores...");
|
Console.WriteLine("Cleaning up all existing vector stores...");
|
||||||
var listRequest = new HttpRequestMessage(HttpMethod.Get, $"{BaseUrl}/vector_stores");
|
var listRequest = new HttpRequestMessage(HttpMethod.Get, $"{BaseUrl}/vector_stores");
|
||||||
listRequest.Headers.Add("OpenAI-Beta", "assistants=v2");
|
listRequest.Headers.Add("OpenAI-Beta", "assistants=v2");
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(listRequest);
|
var response = await _httpClient.SendAsync(listRequest);
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
||||||
var vectorStores = json.RootElement.GetProperty("data");
|
var vectorStores = json.RootElement.GetProperty("data");
|
||||||
|
|
||||||
foreach (var vectorStore in vectorStores.EnumerateArray())
|
foreach (var vectorStore in vectorStores.EnumerateArray())
|
||||||
{
|
{
|
||||||
var id = vectorStore.GetProperty("id").GetString();
|
var id = vectorStore.GetProperty("id").GetString();
|
||||||
var name = vectorStore.TryGetProperty("name", out var nameElement) && nameElement.ValueKind != JsonValueKind.Null
|
var name = vectorStore.TryGetProperty("name", out var nameElement) && nameElement.ValueKind != JsonValueKind.Null
|
||||||
? nameElement.GetString()
|
? nameElement.GetString()
|
||||||
: "Unnamed";
|
: "Unnamed";
|
||||||
|
|
||||||
var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, $"{BaseUrl}/vector_stores/{id}");
|
var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, $"{BaseUrl}/vector_stores/{id}");
|
||||||
deleteRequest.Headers.Add("OpenAI-Beta", "assistants=v2");
|
deleteRequest.Headers.Add("OpenAI-Beta", "assistants=v2");
|
||||||
await _httpClient.SendAsync(deleteRequest);
|
await _httpClient.SendAsync(deleteRequest);
|
||||||
|
|
||||||
Console.WriteLine($"Deleted vector store: {name} ({id})");
|
Console.WriteLine($"Deleted vector store: {name} ({id})");
|
||||||
}
|
}
|
||||||
Console.WriteLine("Vector store cleanup complete!");
|
Console.WriteLine("Vector store cleanup complete!");
|
||||||
|
|
@ -756,99 +682,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region === IMAGE TEXT EXTRACTION ===
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extract text from an image using OpenAI Vision API
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="imageStream">The image file stream</param>
|
|
||||||
/// <param name="fileName">The original filename</param>
|
|
||||||
/// <param name="customPrompt">Optional custom prompt for text extraction</param>
|
|
||||||
/// <returns>Extracted and structured text from the image</returns>
|
|
||||||
public async Task<string?> ExtractTextFromImageAsync(Stream imageStream, string fileName, string? customPrompt = null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Read image bytes from stream
|
|
||||||
byte[] imgBytes;
|
|
||||||
using (var memoryStream = new MemoryStream())
|
|
||||||
{
|
|
||||||
await imageStream.CopyToAsync(memoryStream);
|
|
||||||
imgBytes = memoryStream.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
string base64 = Convert.ToBase64String(imgBytes);
|
|
||||||
|
|
||||||
// Determine image format
|
|
||||||
var extension = Path.GetExtension(fileName).ToLowerInvariant();
|
|
||||||
var mimeType = extension switch
|
|
||||||
{
|
|
||||||
".jpg" or ".jpeg" => "image/jpeg",
|
|
||||||
".png" => "image/png",
|
|
||||||
".gif" => "image/gif",
|
|
||||||
".webp" => "image/webp",
|
|
||||||
_ => "image/jpeg"
|
|
||||||
};
|
|
||||||
|
|
||||||
var prompt = customPrompt ?? "Olvasd ki a szöveget és add vissza szépen strukturálva.";
|
|
||||||
|
|
||||||
var payload = new
|
|
||||||
{
|
|
||||||
model = GetModelName(), // Use the configured model
|
|
||||||
messages = new object[]
|
|
||||||
{
|
|
||||||
new {
|
|
||||||
role = "user",
|
|
||||||
content = new object[]
|
|
||||||
{
|
|
||||||
new { type = "text", text = prompt },
|
|
||||||
new { type = "image_url", image_url = new { url = $"data:{mimeType};base64,{base64}" } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
||||||
});
|
|
||||||
|
|
||||||
var response = await _httpClient.PostAsync(
|
|
||||||
OpenAiEndpoint,
|
|
||||||
new StringContent(json, Encoding.UTF8, "application/json"));
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var error = await response.Content.ReadAsStringAsync();
|
|
||||||
Console.WriteLine($"Image text extraction failed: {error}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultJson = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
using var doc = JsonDocument.Parse(resultJson);
|
|
||||||
|
|
||||||
var inputTokens = doc.RootElement.GetProperty("usage").GetProperty("prompt_tokens").GetInt32();
|
|
||||||
var outputTokens = doc.RootElement.GetProperty("usage").GetProperty("completion_tokens").GetInt32();
|
|
||||||
Console.WriteLine($"USAGE STATS - Image OCR Tokens: {inputTokens} + {outputTokens} = {inputTokens + outputTokens}");
|
|
||||||
|
|
||||||
string text = doc.RootElement
|
|
||||||
.GetProperty("choices")[0]
|
|
||||||
.GetProperty("message")
|
|
||||||
.GetProperty("content")
|
|
||||||
.GetString();
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Error extracting text from image: {ex}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using PDFtoImage;
|
|
||||||
using SkiaSharp;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
public class PdfToImageService
|
|
||||||
{
|
|
||||||
private readonly PdfiumBootstrapper _pdfiumBootstrapper;
|
|
||||||
|
|
||||||
public PdfToImageService(IWebHostEnvironment env)
|
|
||||||
{
|
|
||||||
_pdfiumBootstrapper = new PdfiumBootstrapper(env);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<string>> ConvertPdfToJpgAsync(string pdfPath, string outputFolder)
|
|
||||||
{
|
|
||||||
_pdfiumBootstrapper.EnsurePdfiumLoaded();
|
|
||||||
|
|
||||||
var imagePaths = new List<string>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(outputFolder);
|
|
||||||
|
|
||||||
await Task.Run(() =>
|
|
||||||
{
|
|
||||||
byte[] pdfBytes = File.ReadAllBytes(pdfPath);
|
|
||||||
|
|
||||||
var options = new RenderOptions
|
|
||||||
{
|
|
||||||
Dpi = 300,
|
|
||||||
BackgroundColor = SKColors.White
|
|
||||||
};
|
|
||||||
|
|
||||||
var pdfImages = PDFtoImage.Conversion.ToImages(pdfBytes, options: options);
|
|
||||||
|
|
||||||
int pageNumber = 1;
|
|
||||||
foreach (var page in pdfImages)
|
|
||||||
{
|
|
||||||
var outputPath = Path.Combine(outputFolder, $"page_{pageNumber}.jpg");
|
|
||||||
|
|
||||||
using (var fileStream = File.Create(outputPath))
|
|
||||||
{
|
|
||||||
page.Encode(fileStream, SKEncodedImageFormat.Jpeg, 90);
|
|
||||||
}
|
|
||||||
|
|
||||||
imagePaths.Add(outputPath);
|
|
||||||
page.Dispose();
|
|
||||||
pageNumber++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return imagePaths;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new Exception($"Error converting PDF to images: {ex.Message}", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PdfiumBootstrapper
|
|
||||||
{
|
|
||||||
private readonly IWebHostEnvironment _env;
|
|
||||||
private bool _loaded = false;
|
|
||||||
|
|
||||||
public PdfiumBootstrapper(IWebHostEnvironment env)
|
|
||||||
{
|
|
||||||
_env = env;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnsurePdfiumLoaded()
|
|
||||||
{
|
|
||||||
if (_loaded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var pluginPath = Path.Combine(
|
|
||||||
_env.ContentRootPath,
|
|
||||||
"Plugins",
|
|
||||||
"Misc.FruitBankPlugin" // <- change this
|
|
||||||
);
|
|
||||||
|
|
||||||
var archFolder = Environment.Is64BitProcess ? "win-x64" : "win-x86";
|
|
||||||
var dllPath = Path.Combine(pluginPath, "runtimes", archFolder, "native", "pdfium.dll");
|
|
||||||
|
|
||||||
if (!File.Exists(dllPath))
|
|
||||||
throw new FileNotFoundException($"Pdfium.dll not found: {dllPath}");
|
|
||||||
|
|
||||||
NativeLibrary.Load(dllPath);
|
|
||||||
_loaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<nop-label asp-for="NetWeight" />
|
<nop-label asp-for="NetWeight" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<nop-editor readonly="@Model.IsMeasurable" asp-for="NetWeight" />
|
<nop-editor contenteditable="@Model.IsMeasurable" asp-for="NetWeight" />
|
||||||
<span asp-validation-for="NetWeight"></span>
|
<span asp-validation-for="NetWeight"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -46,24 +46,5 @@
|
||||||
<span asp-validation-for="Tare"></span>
|
<span asp-validation-for="Tare"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<nop-label asp-for="AverageWeight" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<nop-editor asp-for="AverageWeight" />
|
|
||||||
<span asp-validation-for="AverageWeight"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group row">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<nop-label asp-for="AverageWeightTreshold" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-9">
|
|
||||||
<nop-editor asp-for="AverageWeightTreshold" />
|
|
||||||
<span asp-validation-for="AverageWeightTreshold"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Loading…
Reference in New Issue