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 0000000..cae3ce3 Binary files /dev/null and b/Nop.Plugin.Misc.AIPlugin/logo.jpg differ diff --git a/Nop.Plugin.Misc.AIPlugin/plugin.json b/Nop.Plugin.Misc.AIPlugin/plugin.json new file mode 100644 index 0000000..b56ed48 --- /dev/null +++ b/Nop.Plugin.Misc.AIPlugin/plugin.json @@ -0,0 +1,13 @@ +{ + "Group": "Misc", + "FriendlyName": "AIPlugin", + "SystemName": "Misc.AIPlugin", + "Version": "1.00", + "SupportedVersions": [ + "4.80" + ], + "Author": "Adam Gelencser", + "DisplayOrder": 1, + "FileName": "Nop.Plugin.Misc.AIPlugin.dll", + "Description": "" +} \ No newline at end of file