using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
using Nop.Plugin.Misc.FruitBankPlugin.Services;
using Nop.Services.Catalog;
using Nop.Services.Customers;
using Nop.Services.Security;
using Nop.Web.Framework;
using Nop.Web.Framework.Controllers;
using Nop.Web.Framework.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers
{
[AuthorizeAdmin]
[Area(AreaNames.ADMIN)]
[AutoValidateAntiforgeryToken]
public class VoiceOrderController : BasePluginController
{
private readonly IPermissionService _permissionService;
private readonly OpenAIApiService _aiApiService;
private readonly ICustomerService _customerService;
private readonly IProductService _productService;
private readonly FruitBankDbContext _dbContext;
public VoiceOrderController(
IPermissionService permissionService,
OpenAIApiService aiApiService,
ICustomerService customerService,
IProductService productService,
FruitBankDbContext dbContext)
{
_permissionService = permissionService;
_aiApiService = aiApiService;
_customerService = customerService;
_productService = productService;
_dbContext = dbContext;
}
///
/// Display the voice order creation page
///
[HttpGet]
public async Task Create()
{
if (!await _permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
return AccessDeniedView();
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/VoiceOrder/Create.cshtml");
}
///
/// Transcribe audio for partner name and return matching partners
///
[HttpPost]
public async Task TranscribeForPartner(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" });
}
try
{
// Transcribe audio
var transcribedText = await TranscribeAudioFile(audioFile, "hu");
if (string.IsNullOrEmpty(transcribedText))
{
return Json(new { success = false, message = "Failed to transcribe audio" });
}
Console.WriteLine($"[VoiceOrder] Partner transcription: {transcribedText}");
// Search for matching partners
var partners = await SearchPartners(transcribedText);
return Json(new
{
success = true,
transcription = transcribedText,
partners = partners
});
}
catch (Exception ex)
{
Console.WriteLine($"[VoiceOrder] Error in TranscribeForPartner: {ex.Message}");
return Json(new { success = false, message = $"Error: {ex.Message}" });
}
}
///
/// Transcribe audio for products and parse quantities using AI
///
[HttpPost]
public async Task TranscribeForProducts(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" });
}
try
{
// Transcribe audio
var transcribedText = await TranscribeAudioFile(audioFile, "hu");
if (string.IsNullOrEmpty(transcribedText))
{
return Json(new { success = false, message = "Failed to transcribe audio" });
}
Console.WriteLine($"[VoiceOrder] Product transcription: {transcribedText}");
// Parse products and quantities using AI
var parsedProducts = await ParseProductsFromText(transcribedText);
if (parsedProducts == null || parsedProducts.Count == 0)
{
return Json(new
{
success = false,
message = "Could not parse products from transcription",
transcription = transcribedText
});
}
// Enrich with actual product data from database
var enrichedProducts = await EnrichProductData(parsedProducts);
return Json(new
{
success = true,
transcription = transcribedText,
products = enrichedProducts
});
}
catch (Exception ex)
{
Console.WriteLine($"[VoiceOrder] Error in TranscribeForProducts: {ex.Message}");
return Json(new { success = false, message = $"Error: {ex.Message}" });
}
}
#region Helper Methods
///
/// Transcribe audio file using OpenAI Whisper
///
private async Task TranscribeAudioFile(IFormFile audioFile, string language)
{
var fileName = $"voice_order_{DateTime.Now:yyyyMMdd_HHmmss}.webm";
var uploadsFolder = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "voice");
if (!Directory.Exists(uploadsFolder))
{
Directory.CreateDirectory(uploadsFolder);
}
var filePath = Path.Combine(uploadsFolder, fileName);
// Save file temporarily
using (var stream = new FileStream(filePath, FileMode.Create))
{
await audioFile.CopyToAsync(stream);
}
// Transcribe using OpenAI Whisper
string transcribedText;
using (var audioStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
transcribedText = await _aiApiService.TranscribeAudioAsync(audioStream, fileName, language);
}
// Clean up temporary file
try
{
System.IO.File.Delete(filePath);
}
catch { /* Ignore cleanup errors */ }
return transcribedText;
}
///
/// Search for partners matching the transcribed text
/// Uses string-based search first, then semantic AI matching if needed
///
private async Task> SearchPartners(string searchTerm)
{
const int maxResults = 10;
const int minResultsForAI = 3; // If we get fewer than this, use AI semantic search
Console.WriteLine($"[VoiceOrder] Searching partners for: {searchTerm}");
// Step 1: Try string-based search
var customersByCompany = await _customerService.GetAllCustomersAsync(
company: searchTerm,
pageIndex: 0,
pageSize: maxResults);
var customersByName = await _customerService.GetAllCustomersAsync(
firstName: searchTerm,
pageIndex: 0,
pageSize: maxResults);
var customersByLastName = await _customerService.GetAllCustomersAsync(
lastName: searchTerm,
pageIndex: 0,
pageSize: maxResults);
// Combine and deduplicate
var allCustomers = customersByCompany
.Union(customersByName)
.Union(customersByLastName)
.DistinctBy(c => c.Id)
.Take(maxResults)
.ToList();
Console.WriteLine($"[VoiceOrder] String-based search found {allCustomers.Count} partners");
// Step 2: If we don't have enough results, use AI semantic matching
if (allCustomers.Count < minResultsForAI)
{
Console.WriteLine("[VoiceOrder] Using AI semantic matching for partners");
var aiMatches = await SemanticPartnerSearch(searchTerm);
// Merge AI matches with string matches, remove duplicates
allCustomers = allCustomers
.Union(aiMatches)
.DistinctBy(c => c.Id)
.Take(maxResults)
.ToList();
Console.WriteLine($"[VoiceOrder] After AI matching: {allCustomers.Count} partners");
}
// Format results
var result = new List