diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs index e8f6759..94d805e 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/ManagementPageController.cs @@ -13,6 +13,7 @@ using Nop.Web.Areas.Admin.Controllers; using Nop.Web.Framework; using Nop.Web.Framework.Mvc.Filters; using System.Text; +using System.Text.Json; using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor; namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers @@ -231,7 +232,7 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers } var filesList = new List(); - + //iteratation 1: iterate documents to determine their type by AI @@ -258,7 +259,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers //if (file.Length > 0 && file.ContentType == "application/pdf") if (file.Length > 0) { - if (file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)){ + if (file.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase)) + { try { // Open the PDF from the IFormFile's stream directly in memory @@ -336,7 +338,6 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers _logger.Detail(pdfText); } - string analysisPrompt = "Extract the document identification number from this document, determine the type of the " + "document IN ENGLISH from the available list, and return them as JSON: documentNumber, documentType. " + $"Available filetypes: {nameof(DocumentType.Invoice)}, {nameof(DocumentType.ShippingDocument)} , {nameof(DocumentType.OrderConfirmation)}, {nameof(DocumentType.Unknown)}" + @@ -377,65 +378,206 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers // - IF WE DON'T HAVE PARTNERID ALREADY: read partner information // (check if all 3 refers to the same partner) // save partner information to partners table { Id, Name, TaxId, CertificationNumber, PostalCode, Country, State, County, City, Street } - if (partnerId != null) + if (partnerId == null) { - string partnerAnalysisPrompt = "Extract the partner information from this document, and return them as JSON: name, taxId, certificationNumber, postalCode, country, state, county, city, street. " + - "If you can't find information of any of these, return null value for that field."; - //here I can start preparing the file entity - var partnerAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), partnerAnalysisPrompt); - var extractedPartnerData = ParsePartnerDataAIResponse(partnerAnalyzis); + //find partner in DB... there is a serious chance that the partner Name and Taxid determines the partner - if (extractedPartnerData.Name != null) + var partners = await _dbContext.Partners.GetAll().ToListAsync(); + foreach (var partner in partners) { - _logger.Detail("AI Analysis Partner Result:"); - _logger.Detail(extractedPartnerData.Name); + if (pdfText.Contains(partner.Name) || (partner.TaxId != null && pdfText.Contains(partner.TaxId))) + { + partnerId = partner.Id; + _logger.Detail($"Found existing partner in DB: {partner.Name} (ID: {partner.Id})"); + break; + } } - if (extractedPartnerData.TaxId != null) + if (partnerId == null) { - _logger.Detail(extractedPartnerData.TaxId); - } + _logger.Detail("No existing partner found in DB, proceeding to extract partner info via AI."); - if (extractedPartnerData.Country != null) - { + string partnerAnalysisPrompt = "You are an agent of Fruitbank, helping to analyze pdf content. Determine which partner of Fruitbank sent this document, extract their data and return them as JSON: name, taxId, certificationNumber, postalCode, country, state, county, city, street. " + + "If you can't find information of any of these, return null value for that field."; - _logger.Detail(extractedPartnerData.Country); - } + //here I can start preparing the file entity + var partnerAnalyzis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText(pdfText.ToString(), partnerAnalysisPrompt); + var extractedPartnerData = ParsePartnerDataAIResponse(partnerAnalyzis); + + if (extractedPartnerData.Name != null) + { + _logger.Detail("AI Analysis Partner Result:"); + _logger.Detail(extractedPartnerData.Name); + } + + if (extractedPartnerData.TaxId != null) + { + _logger.Detail(extractedPartnerData.TaxId); + } + + if (extractedPartnerData.Country != null) + { + + _logger.Detail(extractedPartnerData.Country); + } + + if (extractedPartnerData.State != null) + { + _logger.Detail(extractedPartnerData.State); + } - if (extractedPartnerData.State != null) - { - _logger.Detail(extractedPartnerData.State); } } - // - save the documents to file system - wwwroot/uploads/orders/order-{orderId}/fileId-documentId.pdf - // where documentId is the number or id IN the document + + //shortcut + + try { - // Create upload directory if it doesn't exist - var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "orders", "order" + shippingDocumentId.ToString(), "documents"); - Directory.CreateDirectory(uploadsPath); - // Generate unique filename - fileName = $"{Guid.NewGuid()}_{file.FileName}"; - var filePath = Path.Combine(uploadsPath, fileName); - // Save file - using (var stream = new FileStream(filePath, FileMode.Create)) + var partnerPrompt = @"Extract the partner/company information from the following text and return ONLY a valid JSON object with these exact fields: { - await file.CopyToAsync(stream); + ""Name"": ""company name"", + ""TaxId"": ""tax identification number"", + ""CertificationNumber"": ""certification number if exists, otherwise empty string"", + ""PostalCode"": ""postal code"", + ""Country"": ""country name"", + ""State"": ""state if exists, otherwise empty string"", + ""County"": ""county if exists, otherwise empty string"", + ""City"": ""city name"", + ""Street"": ""street address"" } + + If a field is not found in the text, use an empty string. Return ONLY the JSON object, no additional text or explanation. + + Text to analyze: + " + pdfText; + + var partnerResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + pdfText, + partnerPrompt + ); + + var partner = JsonSerializer.Deserialize(CleanJsonResponse(partnerResponse), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Step 2: Extract Shipping Document Information + var shippingDocPrompt = @"Extract the shipping document information from the following text and return ONLY a valid JSON object with these exact fields: + { + ""DocumentIdNumber"": ""document ID or reference number if exists, otherwise empty string"", + ""ShippingDate"": ""shipping date in ISO format (yyyy-MM-dd)"", + ""Country"": ""country of origin for pickup"", + ""TotalPallets"": number_of_total_pallets + } + + If a field is not found, use empty string for text fields, current date for ShippingDate, and 0 for TotalPallets. Return ONLY the JSON object, no additional text or explanation. + + Text to analyze: + " + pdfText; + + var shippingDocResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + pdfText, + shippingDocPrompt + ); + + var shippingDocData = JsonSerializer.Deserialize(CleanJsonResponse(shippingDocResponse), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + // Step 3: Extract Shipping Items + var itemsPrompt = @"Extract all shipping items (fruits, vegetables, or products) from the following text and return ONLY a valid JSON array with objects having these exact fields: +[ + { + ""Name"": ""product name"", + ""PalletsOnDocument"": number_of_pallets, + ""QuantityOnDocument"": quantity_count, + ""NetWeightOnDocument"": net_weight_in_kg, + ""GrossWeightOnDocument"": gross_weight_in_kg + } +] + +If a numeric field is not found, use 0. Return ONLY the JSON array, no additional text or explanation. + +Text to analyze: +" + pdfText; + + var itemsResponse = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + pdfText, + itemsPrompt + ); + + var items = JsonSerializer.Deserialize>(CleanJsonResponse(itemsResponse), + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); + + // Prepare result + var result = new ShippingDocumentAnalysisResult + { + Partner = partner, + ShippingDocument = new ShippingDocument + { + DocumentIdNumber = shippingDocData.DocumentIdNumber, + ShippingDate = shippingDocData.ShippingDate, + Country = shippingDocData.Country, + TotalPallets = shippingDocData.TotalPallets + }, + ShippingItems = items + }; + + return Ok(result); + } + catch (JsonException ex) + { + return BadRequest($"Failed to parse AI response: {ex.Message}"); } catch (Exception ex) { - _logger.Error($"Error saving file: {ex.Message}", ex); - //return Json(new { success = false, errorMessage = ex.Message }); - return BadRequest("No files were uploaded."); + return StatusCode(500, $"An error occurred: {ex.Message}"); } + + + + + + + + + + + + + + + // - save the documents to file system - wwwroot/uploads/orders/order-{orderId}/fileId-documentId.pdf + // where documentId is the number or id IN the document + //try + //{ + // // Create upload directory if it doesn't exist + // var uploadsPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "orders", "order" + shippingDocumentId.ToString(), "documents"); + // Directory.CreateDirectory(uploadsPath); + + // // Generate unique filename + // fileName = $"{Guid.NewGuid()}_{file.FileName}"; + // var filePath = Path.Combine(uploadsPath, fileName); + + // // Save file + // using (var stream = new FileStream(filePath, FileMode.Create)) + // { + // await file.CopyToAsync(stream); + // } + //} + //catch (Exception ex) + //{ + // _logger.Error($"Error saving file: {ex.Message}", ex); + // //return Json(new { success = false, errorMessage = ex.Message }); + // return BadRequest("No files were uploaded."); + //} + + // - create a list of documents to read information and to save the document's information to DB // - INSIDE ITERATION: // - IF SHIPPINGDOCUMENT: read shipping information @@ -446,68 +588,70 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers // save document information into DB {financial db table not created yet} // - save the documents to Files Table - { name, extension, type, rawtext } - try - { + //try + //{ - // Analyze PDF with AI to extract structured data - var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( - pdfText.ToString(), - "You work for FruitBank. Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field." - ); + // // Analyze PDF with AI to extract structured data + // var lastPrompt = "You work for FruitBank. Extract the following information from this shipping document and return as JSON: documentDate, recipientName, senderName, invoiceNumber, totalAmount, itemCount, notes. If a field is not found, return null for that field."; - // Parse AI response (assuming it returns JSON) - var extractedData = ParseShippingDocumentAIResponse(aiAnalysis); + // var aiAnalysis = await _aiCalculationService.GetOpenAIPDFAnalysisFromText( + // pdfText, + // lastPrompt + // ); - //TODO: Save document record to database - _logger.Detail("AI Analysis Result:"); - _logger.Detail(extractedData.RecipientName); - _logger.Detail(extractedData.SenderName); - _logger.Detail(extractedData.InvoiceNumber); - _logger.Detail(extractedData.TotalAmount.ToString()); - _logger.Detail(extractedData.ItemCount.ToString()); - _logger.Detail(extractedData.Notes); + // // Parse AI response (assuming it returns JSON) + // var extractedData = ParseShippingDocumentAIResponse(aiAnalysis); + + // //TODO: Save document record to database + // _logger.Detail("AI Analysis Result:"); + // _logger.Detail(extractedData.RecipientName); + // _logger.Detail(extractedData.SenderName); + // _logger.Detail(extractedData.InvoiceNumber); + // _logger.Detail(extractedData.TotalAmount.ToString()); + // _logger.Detail(extractedData.ItemCount.ToString()); + // _logger.Detail(extractedData.Notes); - var documentId = 1; // Replace with: savedDocument.Id + // var documentId = 1; // Replace with: savedDocument.Id - // Return structured document model - var documentModel = new ShippingDocumentModel - { - Id = documentId, - ShippingId = shippingDocumentId, - FileName = file.FileName, - FilePath = $"/uploads/shippingDocuments/{fileName}", - FileSize = (int)(file.Length / 1024), - DocumentDate = extractedData.DocumentDate, - RecipientName = extractedData.RecipientName, - SenderName = extractedData.SenderName, - InvoiceNumber = extractedData.InvoiceNumber, - TotalAmount = extractedData.TotalAmount, - ItemCount = extractedData.ItemCount, - Notes = extractedData.Notes, - RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging - }; + // // Return structured document model + // var documentModel = new ShippingDocumentModel + // { + // Id = documentId, + // ShippingId = shippingDocumentId, + // FileName = file.FileName, + // FilePath = $"/uploads/shippingDocuments/{fileName}", + // FileSize = (int)(file.Length / 1024), + // DocumentDate = extractedData.DocumentDate, + // RecipientName = extractedData.RecipientName, + // SenderName = extractedData.SenderName, + // InvoiceNumber = extractedData.InvoiceNumber, + // TotalAmount = extractedData.TotalAmount, + // ItemCount = extractedData.ItemCount, + // Notes = extractedData.Notes, + // RawAIAnalysis = aiAnalysis // Store the raw AI response for debugging + // }; - // var savedDocument = await _documentService.InsertDocumentAsync(document); + // // var savedDocument = await _documentService.InsertDocumentAsync(document); - // Mock saved document ID + // // Mock saved document ID - //return Json(new - //{ - // success = true, - // document = documentModel - //}); + // //return Json(new + // //{ + // // success = true, + // // document = documentModel + // //}); - } - catch (Exception ex) - { - _logger.Error($"Error uploading file: {ex.Message}", ex); - //return Json(new { success = false, errorMessage = ex.Message }); - return BadRequest("No files were uploaded."); - } + //} + //catch (Exception ex) + //{ + // _logger.Error($"Error uploading file: {ex.Message}", ex); + // //return Json(new { success = false, errorMessage = ex.Message }); + // return BadRequest("No files were uploaded."); + //} } @@ -550,6 +694,47 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers // return Ok(new { message = "Files uploaded successfully." }); //} + private string CleanJsonResponse(string response) + { + // Remove markdown code blocks if present + response = response.Trim(); + if (response.StartsWith("```json")) + { + response = response.Substring(7); + } + else if (response.StartsWith("```")) + { + response = response.Substring(3); + } + + if (response.EndsWith("```")) + { + response = response.Substring(0, response.Length - 3); + } + + return response.Trim(); + } + + public class ShippingDocumentAnalysisRequest + { + public string RawText { get; set; } + } + + public class ShippingDocumentAnalysisResult + { + public Partner Partner { get; set; } + public ShippingDocument ShippingDocument { get; set; } + public List ShippingItems { get; set; } + } + + public class ShippingDocumentDto + { + public string DocumentIdNumber { get; set; } + public DateTime ShippingDate { get; set; } + public string Country { get; set; } + public int TotalPallets { get; set; } + } + private ExtractedDocumentData ParseShippingDocumentAIResponse(string aiResponse) { diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/Edit.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/Edit.cshtml index c8b51f5..97cd999 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/Edit.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Order/Edit.cshtml @@ -23,7 +23,7 @@ var hideNotesBlock = await genericAttributeService.GetAttributeAsync(customer, hideNotesBlockAttributeName); } -
+

@T("Admin.Orders.EditOrderDetails") - @Model.CustomOrderNumber diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Product/List.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Product/List.cshtml index daea847..c0c274b 100644 --- a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Product/List.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Product/List.cshtml @@ -219,6 +219,8 @@ UrlRead = new DataUrl("ProductList", "CustomProduct", null), SearchButtonId = "search-products", Length = Model.PageSize, + Ordering = true, + ServerSide = false, LengthMenu = Model.AvailablePageSizes, Filters = new List { diff --git a/Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml index d78592d..c18c2b1 100644 --- a/Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml @@ -1,6 +1,6 @@ @model Nop.Web.Models.Catalog.ProductOverviewModel -@if (Model != null) +@* @if (Model != null) {
Kérdezz a termékről!
@@ -16,5 +16,5 @@ }); }); -} +} *@ diff --git a/Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml index 0af8412..cdbbc34 100644 --- a/Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml +++ b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml @@ -1,6 +1,6 @@ @model Nop.Web.Models.Catalog.ProductDetailsModel -@if (Model != null) +@* @if (Model != null) {
Kérdezz a termékről!
@@ -16,5 +16,5 @@ }); }); -} +} *@