From e03a9cf6db32bb55de5cb231ac5904e87fd22da0 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 29 Aug 2025 13:35:34 +0200 Subject: [PATCH] AIPLugin --- Nop.Plugin.Misc.AIPlugin/AIPlugin.cs | 164 ++++++++++++++++++ .../Controllers/AIPluginAdminController.cs | 45 +++++ .../Areas/Admin/Models/ConfigureModel.cs | 16 ++ .../Admin/Views/Configure/Configure.cshtml | 22 +++ .../Areas/Admin/Views/_ViewImports.cshtml | 10 ++ .../Components/CustomViewComponent.cs | 18 ++ .../ProductAIWidgetViewComponent.cs | 39 +++++ .../Domains/CustomTable.cs | 8 + .../Infrastructure/PluginNopStartup.cs | 39 +++++ .../Infrastructure/RouteProvider.cs | 24 +++ .../Infrastructure/ViewLocationExpander.cs | 38 ++++ .../Mapping/Builders/PluginBuilder.cs | 20 +++ .../Mapping/NameCompatibility.cs | 16 ++ .../Migrations/SchemaMigration.cs | 19 ++ .../Nop.Plugin.Misc.AIPlugin.csproj | 73 ++++++++ Nop.Plugin.Misc.AIPlugin/OpenAISettings.cs | 10 ++ .../Services/OpenAIService.cs | 45 +++++ .../Views/ProductAIListWidget.cshtml | 20 +++ .../Views/ProductAIWidget.cshtml | 20 +++ .../Views/_ViewImports.cshtml | 15 ++ Nop.Plugin.Misc.AIPlugin/logo.jpg | Bin 0 -> 3118 bytes Nop.Plugin.Misc.AIPlugin/plugin.json | 13 ++ 22 files changed, 674 insertions(+) create mode 100644 Nop.Plugin.Misc.AIPlugin/AIPlugin.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/AIPluginAdminController.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/ConfigureModel.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Configure/Configure.cshtml create mode 100644 Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml create mode 100644 Nop.Plugin.Misc.AIPlugin/Components/CustomViewComponent.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Components/ProductAIWidgetViewComponent.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Domains/CustomTable.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Infrastructure/ViewLocationExpander.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Mapping/Builders/PluginBuilder.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Mapping/NameCompatibility.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Migrations/SchemaMigration.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.AIPlugin.csproj create mode 100644 Nop.Plugin.Misc.AIPlugin/OpenAISettings.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Services/OpenAIService.cs create mode 100644 Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml create mode 100644 Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml create mode 100644 Nop.Plugin.Misc.AIPlugin/Views/_ViewImports.cshtml create mode 100644 Nop.Plugin.Misc.AIPlugin/logo.jpg create mode 100644 Nop.Plugin.Misc.AIPlugin/plugin.json diff --git a/Nop.Plugin.Misc.AIPlugin/AIPlugin.cs b/Nop.Plugin.Misc.AIPlugin/AIPlugin.cs new file mode 100644 index 0000000..9998ffa --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/AIPlugin.cs @@ -0,0 +1,164 @@ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Nop.Plugin.Misc.AIPlugin.Components; +using Nop.Services.Cms; +using Nop.Services.Configuration; +using Nop.Services.Localization; +using Nop.Services.Plugins; +using Nop.Services.Security; +using Nop.Web.Framework.Infrastructure; +using Nop.Web.Framework.Menu; + + +namespace Nop.Plugin.Misc.AIPlugin +{ + /// + /// Main plugin class + /// + public class AIPlugin : BasePlugin, IWidgetPlugin + { + protected readonly IActionContextAccessor _actionContextAccessor; + private readonly ISettingService _settingService; + //private readonly IWebHelper _webHelper; + protected readonly IPermissionService _permissionService; + protected readonly ILocalizationService _localizationService; + protected readonly IUrlHelperFactory _urlHelperFactory; + private readonly IAdminMenu _adminMenu; + + //handle AdminMenuCreatedEvent + + + public AIPlugin(IActionContextAccessor actionContextAccessor, + ISettingService settingService, + //IWebHelper webHelper, + ILocalizationService localizationService, + IPermissionService permissionService, + IUrlHelperFactory urlHelperFactory, + IAdminMenu adminMenu) + { + _actionContextAccessor = actionContextAccessor; + _settingService = settingService; + //_webHelper = webHelper; + _localizationService = localizationService; + _urlHelperFactory = urlHelperFactory; + _adminMenu = adminMenu; + _permissionService = permissionService; + } + + // --- INSTALL --- + public override async Task InstallAsync() + { + // Default settings + var settings = new OpenAiSettings + { + ApiKey = string.Empty + }; + await _settingService.SaveSettingAsync(settings); + + await base.InstallAsync(); + } + + // --- UNINSTALL --- + public override async Task UninstallAsync() + { + await _settingService.DeleteSettingAsync(); + await base.UninstallAsync(); + } + + // --- WIDGETS --- + public bool HideInWidgetList => false; + + public Task> GetWidgetZonesAsync() + { + return Task.FromResult>(new List { PublicWidgetZones.ProductBoxAddinfoBefore, PublicWidgetZones.ProductDetailsBottom }); + } + + //public string GetWidgetViewComponentName(string widgetZone) + //{ + // return "ProductAIWidget"; // A ViewComponent neve + //} + + // --- ADMIN MENÜ --- + //public async Task ManageSiteMapAsync(AdminMenuItem rootNode) + //{ + // if (!await _permissionService.AuthorizeAsync(StandardPermission.Configuration.MANAGE_PLUGINS)) + // return; + + // var pluginNode = new AdminMenuItem + // { + // SystemName = "AIPlugin.Configure", + // Title = "AI Assistant", + // Url = $"{_webHelper.GetStoreLocation()}Admin/AIPluginAdmin/Configure", + // Visible = true + // }; + // rootNode.ChildNodes.Add(pluginNode); + // //return Task.CompletedTask; + //} + + public async Task ManageSiteMapAsync(AdminMenuItem rootNode) + { + if (!await _permissionService.AuthorizeAsync(StandardPermission.Configuration.MANAGE_PLUGINS)) + return; + + var configurationItem = rootNode.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Configuration")); + if (configurationItem is null) + return; + + var shippingItem = configurationItem.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Shipping")); + var widgetsItem = configurationItem.ChildNodes.FirstOrDefault(node => node.SystemName.Equals("Widgets")); + if (shippingItem is null && widgetsItem is null) + return; + + var index = shippingItem is not null ? configurationItem.ChildNodes.IndexOf(shippingItem) : -1; + if (index < 0) + index = widgetsItem is not null ? configurationItem.ChildNodes.IndexOf(widgetsItem) : -1; + if (index < 0) + return; + + configurationItem.ChildNodes.Insert(index + 1, new AdminMenuItem + { + + Visible = true, + SystemName = "AI plugins", + Title = await _localizationService.GetResourceAsync("Plugins.Misc.SignalRApi.Menu.AI"), + IconClass = "far fa-dot-circle", + ChildNodes = new List + { + new() + { + // SystemName = "AIPlugin.Configure", + // Title = "AI Assistant", + // Url = $"{_webHelper.GetStoreLocation()}Admin/AIPluginAdmin/Configure", + // Visible = true + Visible = true, + SystemName = PluginDescriptor.SystemName, + Title = PluginDescriptor.FriendlyName, + IconClass = "far fa-circle", + Url = _adminMenu.GetMenuItemUrl("AIPlugin", "Configure"), + //Url = "Admin/SignalRApi/Configure", + //ControllerName = "SignalRApi", + //ActionName = "Configure", + //RouteValues = new RouteValueDictionary { { "area", AreaNames.ADMIN } } + } + } + }); + } + + public override string GetConfigurationPageUrl() + { + return _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext).RouteUrl("Plugin.Misc.AIPlugin.Configure"); + } + + public Type GetWidgetViewComponent(string widgetZone) + { + if (widgetZone is null) + throw new ArgumentNullException(nameof(widgetZone)); + + var zones = GetWidgetZonesAsync().Result; + + return zones.Any(widgetZone.Equals) ? typeof(ProductAIWidgetViewComponent) : null; + } + } +} diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/AIPluginAdminController.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/AIPluginAdminController.cs new file mode 100644 index 0000000..6cba1d1 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Controllers/AIPluginAdminController.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using Nop.Plugin.Misc.AIPlugin.Areas.Admin.Models; +using Nop.Plugin.Misc.AIPlugin; +//using Nop.Plugin.Misc.AIPlugin; +using Nop.Services.Configuration; +using Nop.Web.Framework.Controllers; +using Nop.Services.Messages; + +namespace Nop.Plugin.Misc.AIPlugin.Controllers +{ + [Area("Admin")] + public class AIPluginAdminController : BasePluginController + { + private readonly INotificationService _notificationService; + private readonly ISettingService _settingService; + private readonly OpenAiSettings _settings; + + public AIPluginAdminController(INotificationService notificationService, ISettingService settingService, OpenAiSettings settings) + { + _notificationService = notificationService; + _settingService = settingService; + _settings = settings; + } + + [HttpGet] + public IActionResult Configure() + { + var model = new ConfigureModel + { + ApiKey = _settings.ApiKey + }; + return View("~/Plugins/Misc.AIPlugin/Views/Configure/Configure.cshtml", model); + } + + [HttpPost] + public async Task Configure(ConfigureModel model) + { + _settings.ApiKey = model.ApiKey; + await _settingService.SaveSettingAsync(_settings); + _notificationService.SuccessNotification("Beállítások mentve."); + return RedirectToAction("Configure"); + } + } +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/ConfigureModel.cs b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/ConfigureModel.cs new file mode 100644 index 0000000..d74e084 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Models/ConfigureModel.cs @@ -0,0 +1,16 @@ +using Nop.Web.Framework.Mvc.ModelBinding; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nop.Plugin.Misc.AIPlugin.Areas.Admin.Models +{ + public record ConfigureModel + { + [NopResourceDisplayName("Plugins.AIPlugin.Fields.ApiKey")] + public string ApiKey { get; set; } + } +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Configure/Configure.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Configure/Configure.cshtml new file mode 100644 index 0000000..3abc8da --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/Configure/Configure.cshtml @@ -0,0 +1,22 @@ +@model Nop.Plugin.Misc.AIPlugin.Models.ConfigureModel + +@{ + Layout = "_AdminLayout"; + Html.SetActiveMenuItemSystemName("AiAssistant.Configure"); +} + +
+
+

AI Assistant Plugin - Beállítások

+
+
+
+
+ + +
+ +
+
+
+ diff --git a/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml new file mode 100644 index 0000000..270744e --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/_ViewImports.cshtml @@ -0,0 +1,10 @@ +@inherits Nop.Web.Framework.Mvc.Razor.NopRazorPage +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Nop.Web.Framework + +@using Microsoft.AspNetCore.Mvc.ViewFeatures +@using Nop.Web.Framework.UI +@using Nop.Web.Framework.Extensions +@using System.Text.Encodings.Web +@using Nop.Services.Events +@using Nop.Web.Framework.Events \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Components/CustomViewComponent.cs b/Nop.Plugin.Misc.AIPlugin/Components/CustomViewComponent.cs new file mode 100644 index 0000000..1e2453a --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Components/CustomViewComponent.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using Nop.Web.Framework.Components; + +namespace Nop.Plugin.Misc.AIPlugin.Components; + +[ViewComponent(Name = "Custom")] +public class CustomViewComponent : NopViewComponent +{ + public CustomViewComponent() + { + + } + + public IViewComponentResult Invoke(int productId) + { + throw new NotImplementedException(); + } +} diff --git a/Nop.Plugin.Misc.AIPlugin/Components/ProductAIWidgetViewComponent.cs b/Nop.Plugin.Misc.AIPlugin/Components/ProductAIWidgetViewComponent.cs new file mode 100644 index 0000000..7ee276e --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Components/ProductAIWidgetViewComponent.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using Nop.Web.Framework.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nop.Plugin.Misc.AIPlugin.Components +{ + + [ViewComponent(Name = "ProductAIWidget")] + public class ProductAIWidgetViewComponent : NopViewComponent + { + public IViewComponentResult Invoke(string widgetZone, object additionalData) + { + if(additionalData is Nop.Web.Models.Catalog.ProductOverviewModel) + { + var product = additionalData as Nop.Web.Models.Catalog.ProductOverviewModel; + + if (product == null) + return Content(""); // ne rendereljen semmit, ha nincs product + return View("~/Plugins/Misc.AIPlugin/Views/ProductAIListWidget.cshtml", product); + } + else if (additionalData is Nop.Web.Models.Catalog.ProductDetailsModel) + { + var product = additionalData as Nop.Web.Models.Catalog.ProductDetailsModel; + + if (product == null) + return Content(""); // ne rendereljen semmit, ha nincs product + return View("~/Plugins/Misc.AIPlugin/Views/ProductAIWidget.cshtml", product); + } + else { + return Content(""); // ne rendereljen semmit, ha nem productDetailModel vagy productOverviewModel + } + + } + } +} diff --git a/Nop.Plugin.Misc.AIPlugin/Domains/CustomTable.cs b/Nop.Plugin.Misc.AIPlugin/Domains/CustomTable.cs new file mode 100644 index 0000000..6628c61 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Domains/CustomTable.cs @@ -0,0 +1,8 @@ +using Nop.Core; + +namespace Nop.Plugin.Misc.AIPlugin.Domains; + +public partial class CustomTable : BaseEntity +{ + +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs b/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs new file mode 100644 index 0000000..5fb9632 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Infrastructure/PluginNopStartup.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Nop.Core.Infrastructure; + +namespace Nop.Plugin.Misc.AIPlugin.Infrastructure; + +public class PluginNopStartup : INopStartup +{ + /// + /// Add and configure any of the middleware + /// + /// Collection of service descriptors + /// Configuration of the application + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.Configure(options => + { + options.ViewLocationExpanders.Add(new ViewLocationExpander()); + }); + + //register services and interfaces + //services.AddScoped(); + } + + /// + /// Configure the using of added middleware + /// + /// Builder for configuring an application's request pipeline + public void Configure(IApplicationBuilder application) + { + } + + /// + /// Gets order of this startup configuration implementation + /// + public int Order => 3000; +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs b/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs new file mode 100644 index 0000000..6bd7294 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Infrastructure/RouteProvider.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Routing; +using Nop.Web.Framework.Mvc.Routing; + +namespace Nop.Plugin.Misc.AIPlugin.Infrastructure; + +/// +/// Represents plugin route provider +/// +public class RouteProvider : IRouteProvider +{ + /// + /// Register routes + /// + /// Route builder + public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder) + { + + } + + /// + /// Gets a priority of route provider + /// + public int Priority => 0; +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Infrastructure/ViewLocationExpander.cs b/Nop.Plugin.Misc.AIPlugin/Infrastructure/ViewLocationExpander.cs new file mode 100644 index 0000000..c4266bf --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Infrastructure/ViewLocationExpander.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Mvc.Razor; + +namespace Nop.Plugin.Misc.AIPlugin.Infrastructure; + +public class ViewLocationExpander : IViewLocationExpander +{ + /// + /// Invoked by a to determine the values that would be consumed by this instance + /// of . The calculated values are used to determine if the view location + /// has changed since the last time it was located. + /// + /// The for the current view location + /// expansion operation. + public void PopulateValues(ViewLocationExpanderContext context) + { + } + + /// + /// Invoked by a to determine potential locations for a view. + /// + /// The for the current view location + /// expansion operation. + /// The sequence of view locations to expand. + /// A list of expanded view locations. + public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) + { + if (context.AreaName == "Admin") + { + viewLocations = new[] { $"/Plugins/Nop.Plugin.Misc.AIPlugin/Areas/Admin/Views/{context.ControllerName}/{context.ViewName}.cshtml" }.Concat(viewLocations); + } + else + { + viewLocations = new[] { $"/Plugins/Nop.Plugin.Misc.AIPlugin/Views/{context.ControllerName}/{context.ViewName}.cshtml" }.Concat(viewLocations); + } + + return viewLocations; + } +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Mapping/Builders/PluginBuilder.cs b/Nop.Plugin.Misc.AIPlugin/Mapping/Builders/PluginBuilder.cs new file mode 100644 index 0000000..18e17fd --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Mapping/Builders/PluginBuilder.cs @@ -0,0 +1,20 @@ +using FluentMigrator.Builders.Create.Table; +using Nop.Data.Mapping.Builders; +using Nop.Plugin.Misc.AIPlugin.Domains; + +namespace Nop.Plugin.Misc.AIPlugin.Mapping.Builders; + +public class PluginBuilder : NopEntityBuilder +{ + #region Methods + + /// + /// Apply entity configuration + /// + /// Create table expression builder + public override void MapEntity(CreateTableExpressionBuilder table) + { + } + + #endregion +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Mapping/NameCompatibility.cs b/Nop.Plugin.Misc.AIPlugin/Mapping/NameCompatibility.cs new file mode 100644 index 0000000..f4ab688 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Mapping/NameCompatibility.cs @@ -0,0 +1,16 @@ +using Nop.Data.Mapping; + +namespace Nop.Plugin.Misc.AIPlugin.Mapping; + +public partial class NameCompatibility : INameCompatibility +{ + /// + /// Gets table name for mapping with the type + /// + public Dictionary TableNames => new(); + + /// + /// Gets column name for mapping with the entity's property and type + /// + public Dictionary<(Type, string), string> ColumnName => new(); +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Migrations/SchemaMigration.cs b/Nop.Plugin.Misc.AIPlugin/Migrations/SchemaMigration.cs new file mode 100644 index 0000000..8df4da1 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Migrations/SchemaMigration.cs @@ -0,0 +1,19 @@ +using FluentMigrator; +using Nop.Data.Extensions; +using Nop.Data.Migrations; +using Nop.Plugin.Misc.AIPlugin.Domains; + +namespace Nop.Plugin.Misc.AIPlugin.Migrations; + +//2024-10-31 +[NopMigration("2025-08-29 12:07:22", "Nop.Plugin.Misc.AIPlugin schema", MigrationProcessType.Installation)] +public class SchemaMigration : AutoReversingMigration +{ + /// + /// Collect the UP migration expressions + /// + public override void Up() + { + Create.TableFor(); + } +} \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.AIPlugin.csproj b/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.AIPlugin.csproj new file mode 100644 index 0000000..8a0f3af --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Nop.Plugin.Misc.AIPlugin.csproj @@ -0,0 +1,73 @@ + + + + net9.0 + $(SolutionDir)\Presentation\Nop.Web\Plugins\Misc.AIPlugin + $(OutputPath) + + false + enable + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + + + + + + diff --git a/Nop.Plugin.Misc.AIPlugin/OpenAISettings.cs b/Nop.Plugin.Misc.AIPlugin/OpenAISettings.cs new file mode 100644 index 0000000..2b2d7dc --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/OpenAISettings.cs @@ -0,0 +1,10 @@ +using global::Nop.Core.Configuration; + + +namespace Nop.Plugin.Misc.AIPlugin +{ + public class OpenAiSettings : ISettings + { + public string ApiKey { get; set; } + } +} diff --git a/Nop.Plugin.Misc.AIPlugin/Services/OpenAIService.cs b/Nop.Plugin.Misc.AIPlugin/Services/OpenAIService.cs new file mode 100644 index 0000000..407b069 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Services/OpenAIService.cs @@ -0,0 +1,45 @@ +using Nop.Plugin.Misc.AIPlugin; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Nop.Services.Configuration; + +namespace Nop.Plugin.Misc.AIPlugin.Services +{ + + public class OpenAiService + { + private readonly OpenAiSettings _settings; + + public OpenAiService(OpenAiSettings settings) + { + _settings = settings; + } + + public async Task AskAsync(string prompt) + { + using var client = new HttpClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _settings.ApiKey); + + var body = new + { + model = "gpt-4o-mini", + messages = new[] + { + new { role = "system", content = "You are a helpful assistant." }, + new { role = "user", content = prompt } + } + }; + + var content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); + var response = await client.PostAsync("https://api.openai.com/v1/chat/completions", content); + + var json = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(json); + + return doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString(); + } + } +} diff --git a/Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml new file mode 100644 index 0000000..d78592d --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIListWidget.cshtml @@ -0,0 +1,20 @@ +@model Nop.Web.Models.Catalog.ProductOverviewModel + +@if (Model != null) +{ +
+
Kérdezz a termékről!
+ + +
+
+ + +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml new file mode 100644 index 0000000..0af8412 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Views/ProductAIWidget.cshtml @@ -0,0 +1,20 @@ +@model Nop.Web.Models.Catalog.ProductDetailsModel + +@if (Model != null) +{ +
+
Kérdezz a termékről!
+ + +
+
+ + +} + diff --git a/Nop.Plugin.Misc.AIPlugin/Views/_ViewImports.cshtml b/Nop.Plugin.Misc.AIPlugin/Views/_ViewImports.cshtml new file mode 100644 index 0000000..248fb2b --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/Views/_ViewImports.cshtml @@ -0,0 +1,15 @@ +@inherits Nop.Web.Framework.Mvc.Razor.NopRazorPage +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Nop.Web.Framework + +@using Microsoft.AspNetCore.Mvc.ViewFeatures +@using Nop.Web.Framework.UI +@using Nop.Web.Framework.Extensions +@using System.Text.Encodings.Web +@using Nop.Services.Events +@using Nop.Web.Framework.Events +@using Nop.Web.Framework.Infrastructure +@using Nop.Plugin.Misc.AIPlugin +@* @using Nop.Plugin.Misc.AIPlugin.Models *@ +@using Nop.Plugin.Misc.AIPlugin.Services +@* @using Nop.Plugin.Misc.AIPlugin.Hubs *@ \ No newline at end of file diff --git a/Nop.Plugin.Misc.AIPlugin/logo.jpg b/Nop.Plugin.Misc.AIPlugin/logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cae3ce3f17ad9ee8604dab7af6726378985e81ae GIT binary patch literal 3118 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