From 561f58784c06400ec805e8e457ab412e19859c32 Mon Sep 17 00:00:00 2001 From: mmrbnjd Date: Mon, 18 Aug 2025 17:30:02 +0330 Subject: [PATCH] ... --- Hushian.Application/Contracts/Iaiass.cs | 14 + Hushian.Application/Models/aiRequestModel.cs | 29 + Hushian.Application/Models/aia/aiSetting.cs | 14 + Hushian.Application/Services/AIService.cs | 119 +++-- Hushian.sln | 7 + .../InfrastractureServicesRegistration.cs | 6 +- Infrastructure/Infrastructure/openAI.cs | 65 +++ Presentation/AIAss/AIAss.csproj | 17 + Presentation/AIAss/Program.cs | 14 + .../AIAss/Properties/launchSettings.json | 23 + Presentation/AIAss/Protos/greet.proto | 21 + Presentation/AIAss/Services/GreeterService.cs | 21 + .../AIAss/appsettings.Development.json | 8 + Presentation/AIAss/appsettings.json | 14 + .../Pages/FromUserSide/AIChat.razor | 498 ++++++++++-------- 15 files changed, 588 insertions(+), 282 deletions(-) create mode 100644 Hushian.Application/Contracts/Iaiass.cs create mode 100644 Hushian.Application/Models/aiRequestModel.cs create mode 100644 Hushian.Application/Models/aia/aiSetting.cs create mode 100644 Infrastructure/Infrastructure/openAI.cs create mode 100644 Presentation/AIAss/AIAss.csproj create mode 100644 Presentation/AIAss/Program.cs create mode 100644 Presentation/AIAss/Properties/launchSettings.json create mode 100644 Presentation/AIAss/Protos/greet.proto create mode 100644 Presentation/AIAss/Services/GreeterService.cs create mode 100644 Presentation/AIAss/appsettings.Development.json create mode 100644 Presentation/AIAss/appsettings.json diff --git a/Hushian.Application/Contracts/Iaiass.cs b/Hushian.Application/Contracts/Iaiass.cs new file mode 100644 index 0000000..83e872a --- /dev/null +++ b/Hushian.Application/Contracts/Iaiass.cs @@ -0,0 +1,14 @@ +using Hushian.Application.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hushian.Application.Contracts +{ + public interface Iaiass + { + Task> SendQuestion(aiRequestModel Request); + } +} diff --git a/Hushian.Application/Models/aiRequestModel.cs b/Hushian.Application/Models/aiRequestModel.cs new file mode 100644 index 0000000..88d7c91 --- /dev/null +++ b/Hushian.Application/Models/aiRequestModel.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hushian.Application.Models +{ + public class aiRequestModel + { + public aiRequestModel(string question, string[] prompts) + { + this.question = question; + this.prompts = prompts; + } + + public string question { get; set; } + public string[] prompts { get; set; } + public override string ToString() + { + string str = ""; + foreach (var item in prompts) + { + str += '\n'+item; + } + return str; + } + } +} diff --git a/Hushian.Application/Models/aia/aiSetting.cs b/Hushian.Application/Models/aia/aiSetting.cs new file mode 100644 index 0000000..734ee14 --- /dev/null +++ b/Hushian.Application/Models/aia/aiSetting.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hushian.Application.Models.aia +{ + public class aiSetting + { + public string url { get; set; } + public string apitoken { get; set; } + } +} diff --git a/Hushian.Application/Services/AIService.cs b/Hushian.Application/Services/AIService.cs index 010d819..5c66eee 100644 --- a/Hushian.Application/Services/AIService.cs +++ b/Hushian.Application/Services/AIService.cs @@ -1,5 +1,6 @@ using AutoMapper; using Common.Dtos; +using Hushian.Application.Contracts; using Hushian.Application.Contracts.Persistence; using Hushian.Application.Models; using Hushian.Domain.Entites; @@ -8,65 +9,79 @@ using System.Linq; namespace Hushian.Application.Services { - public class AIService - { - private readonly IGenericRepository _aiaRepository; - private readonly IMapper _mapper; + public class AIService + { + private readonly IGenericRepository _aiaRepository; + private readonly IGenericRepository _promprtRepository; + private readonly Iaiass _aiass; - public AIService(IGenericRepository aiaRepository, IMapper mapper) - { - _aiaRepository = aiaRepository; - _mapper = mapper; - } - public async Task> NewRequest - (aiNewResponseDto dto) - { - ResponseBase response = new(); - try - { - string responeai="همین جوری"; - bool sucessresponseai =!false; - + public AIService(IGenericRepository aiaRepository, Iaiass aiass, IGenericRepository promprtRepository) + { + _aiaRepository = aiaRepository; + _aiass = aiass; + _promprtRepository = promprtRepository; + } + public async Task> NewRequest + (aiNewResponseDto dto) + { + ResponseBase response = new(); + try + { + string responeai = ""; + bool sucessresponseai = false; + + var prompts = await _promprtRepository.Get() + .Where(w => w.CompanyID == dto.companyId).Select(s => s.Test).ToArrayAsync(); + var requsetai= await _aiass.SendQuestion(new aiRequestModel(dto.requestText,prompts)); + if(requsetai.Success) + { + sucessresponseai = true; + responeai = requsetai.Value; + } + else + { + response.Errors = requsetai.Errors; + } var entity = new AIA - { - CompanyID = dto.companyId, - aiKeyUser = dto.aiKeyUser, - Request = dto.requestText, - Response = responeai, - Cdatetime = DateTime.Now - }; - var added = await _aiaRepository.ADD(entity); + { + CompanyID = dto.companyId, + aiKeyUser = dto.aiKeyUser, + Request = dto.requestText, + Response = responeai, + Cdatetime = DateTime.Now + }; + var added = await _aiaRepository.ADD(entity); - if(sucessresponseai) - response.Value = new() { dateTime=added.Cdatetime,responseText=added.Response,requestText=added.Request}; - response.Success = sucessresponseai; - } - catch (Exception) - { - response.Errors.Add("خطا در ذخیره سازی"); - } - return response; - } + if (sucessresponseai) + response.Value = new() { dateTime = added.Cdatetime, responseText = added.Response, requestText = added.Request }; + response.Success = sucessresponseai; + } + catch (Exception) + { + response.Errors.Add("خطا در ذخیره سازی"); + } + return response; + } - public async Task> GetCurrent(int companyId, string aiKeyUser) - { - return await _aiaRepository - .Get() - .Where(w => w.CompanyID == companyId && w.aiKeyUser == aiKeyUser && w.Cdatetime.AddHours(3) >= DateTime.Now) - .OrderBy(o => o.ID) - .Select(s=>new aiResponseDto() { responseText = s.Response, requestText = s.Request,dateTime=s.Cdatetime}) - .ToListAsync(); - } + public async Task> GetCurrent(int companyId, string aiKeyUser) + { + return await _aiaRepository + .Get() + .Where(w => w.CompanyID == companyId && w.aiKeyUser == aiKeyUser && w.Cdatetime.AddHours(3) >= DateTime.Now) + .OrderBy(o => o.ID) + .Select(s => new aiResponseDto() { responseText = s.Response, requestText = s.Request, dateTime = s.Cdatetime }) + .ToListAsync(); + } - //public async Task DeleteEntry(int id, int companyId) - //{ - // var entity = await _aiaRepository.Get().FirstOrDefaultAsync(f => f.ID == id && f.CompanyID == companyId); - // if (entity == null) return false; - // return await _aiaRepository.DELETE(entity); - //} - } + //public async Task DeleteEntry(int id, int companyId) + //{ + // var entity = await _aiaRepository.Get().FirstOrDefaultAsync(f => f.ID == id && f.CompanyID == companyId); + // if (entity == null) return false; + // return await _aiaRepository.DELETE(entity); + //} + } } diff --git a/Hushian.sln b/Hushian.sln index d104999..3e7ece3 100644 --- a/Hushian.sln +++ b/Hushian.sln @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hushian.WebApi", "Presentat EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HushianWebApp", "Presentation\HushianWebApp\HushianWebApp.csproj", "{80D865DC-1CCD-9C25-5DAF-153E7B33408E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AIAss", "Presentation\AIAss\AIAss.csproj", "{AF6AE286-63CF-4A8A-A0B4-DBDA047FC9AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +57,10 @@ Global {80D865DC-1CCD-9C25-5DAF-153E7B33408E}.Debug|Any CPU.Build.0 = Debug|Any CPU {80D865DC-1CCD-9C25-5DAF-153E7B33408E}.Release|Any CPU.ActiveCfg = Release|Any CPU {80D865DC-1CCD-9C25-5DAF-153E7B33408E}.Release|Any CPU.Build.0 = Release|Any CPU + {AF6AE286-63CF-4A8A-A0B4-DBDA047FC9AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF6AE286-63CF-4A8A-A0B4-DBDA047FC9AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF6AE286-63CF-4A8A-A0B4-DBDA047FC9AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF6AE286-63CF-4A8A-A0B4-DBDA047FC9AE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -64,6 +70,7 @@ Global {05D292C2-BB17-4524-B1F2-8A2B6B213C6A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {37AD460D-1663-4755-AC15-703BFFBF20D2} = {F9F2A965-B4E4-4990-B547-F18200AF2631} {80D865DC-1CCD-9C25-5DAF-153E7B33408E} = {F9F2A965-B4E4-4990-B547-F18200AF2631} + {AF6AE286-63CF-4A8A-A0B4-DBDA047FC9AE} = {F9F2A965-B4E4-4990-B547-F18200AF2631} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F0E59B62-9EDF-47DC-AAFD-F841443D0AAE} diff --git a/Infrastructure/Infrastructure/InfrastractureServicesRegistration.cs b/Infrastructure/Infrastructure/InfrastractureServicesRegistration.cs index e7b1947..f34d18a 100644 --- a/Infrastructure/Infrastructure/InfrastractureServicesRegistration.cs +++ b/Infrastructure/Infrastructure/InfrastractureServicesRegistration.cs @@ -1,4 +1,6 @@ using Common.Contracts.Infrastructure; +using Hushian.Application.Contracts; +using Hushian.Application.Models.aia; using Hushian.Application.Models.Message; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -17,8 +19,8 @@ namespace Hushian.Infrastructure services.AddTransient(); - //services.Configure(configuration.GetSection("aiSettings")); - //services.AddTransient(); + services.Configure(configuration.GetSection("aiSettings")); + services.AddTransient(); services.AddScoped(c => new Melipayamak.RestClient(configuration.GetSection("MessageSettings:UserName").Value, configuration.GetSection("MessageSettings:Password").Value)); return services; diff --git a/Infrastructure/Infrastructure/openAI.cs b/Infrastructure/Infrastructure/openAI.cs new file mode 100644 index 0000000..83dc5cd --- /dev/null +++ b/Infrastructure/Infrastructure/openAI.cs @@ -0,0 +1,65 @@ +using Hushian.Application.Contracts; +using Hushian.Application.Models; +using Hushian.Application.Models.aia; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Hushian.Infrastructure +{ + public class openAI : Iaiass + { + const string model = "gpt-4o-mini"; + private aiSetting _aiSettings; + public openAI(IOptions aiSettings) + { + _aiSettings = aiSettings.Value; + } + public async Task> SendQuestion(aiRequestModel Request) + { + var Response = new ResponseBase(); + try + { + using var client = new HttpClient(); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", _aiSettings.apitoken); + + var content = new + { + model = model, + messages = new[] + { + new { role = "system", content = "شما یک دستیار پاسخگو به سوالات هستید." }, + new { role = "user", content = $"با توجه به این متن:{Request.ToString()}به این سوال پاسخ بده:{ Request.question}" }, + new { role = "system", content = "به سوالات غیره متن بالا پاسخ نده و بگو در این زمینه اطلاعی ندارم" } + } + }; + + var response = await client.PostAsync(_aiSettings.url, + new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json")); + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(); + using var doc = JsonDocument.Parse(json); + Response.Value = doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString(); + Response.Success = true; + } + else + { + Response.Errors.Add("خطا در ارتباط سرور ai"); + } + } + catch (Exception ex) + { + Response.Errors.Add("خطا مدیریت نشده در ارتباط سرور ai"); + } + + return Response; + } + } +} diff --git a/Presentation/AIAss/AIAss.csproj b/Presentation/AIAss/AIAss.csproj new file mode 100644 index 0000000..6ae0557 --- /dev/null +++ b/Presentation/AIAss/AIAss.csproj @@ -0,0 +1,17 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + diff --git a/Presentation/AIAss/Program.cs b/Presentation/AIAss/Program.cs new file mode 100644 index 0000000..3b0378a --- /dev/null +++ b/Presentation/AIAss/Program.cs @@ -0,0 +1,14 @@ +using AIAss.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddGrpc(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +app.MapGrpcService(); +app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + +app.Run(); diff --git a/Presentation/AIAss/Properties/launchSettings.json b/Presentation/AIAss/Properties/launchSettings.json new file mode 100644 index 0000000..b612717 --- /dev/null +++ b/Presentation/AIAss/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5010", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7015;http://localhost:5010", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Presentation/AIAss/Protos/greet.proto b/Presentation/AIAss/Protos/greet.proto new file mode 100644 index 0000000..e21061e --- /dev/null +++ b/Presentation/AIAss/Protos/greet.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option csharp_namespace = "AIAss"; + +package greet; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply); +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings. +message HelloReply { + string message = 1; +} diff --git a/Presentation/AIAss/Services/GreeterService.cs b/Presentation/AIAss/Services/GreeterService.cs new file mode 100644 index 0000000..94c3853 --- /dev/null +++ b/Presentation/AIAss/Services/GreeterService.cs @@ -0,0 +1,21 @@ +using Grpc.Core; +using AIAss; + +namespace AIAss.Services; + +public class GreeterService : Greeter.GreeterBase +{ + private readonly ILogger _logger; + public GreeterService(ILogger logger) + { + _logger = logger; + } + + public override Task SayHello(HelloRequest request, ServerCallContext context) + { + return Task.FromResult(new HelloReply + { + Message = "Hello " + request.Name + }); + } +} diff --git a/Presentation/AIAss/appsettings.Development.json b/Presentation/AIAss/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/Presentation/AIAss/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Presentation/AIAss/appsettings.json b/Presentation/AIAss/appsettings.json new file mode 100644 index 0000000..1aef507 --- /dev/null +++ b/Presentation/AIAss/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + } +} diff --git a/Presentation/HushianWebApp/Pages/FromUserSide/AIChat.razor b/Presentation/HushianWebApp/Pages/FromUserSide/AIChat.razor index dacc822..8eeab96 100644 --- a/Presentation/HushianWebApp/Pages/FromUserSide/AIChat.razor +++ b/Presentation/HushianWebApp/Pages/FromUserSide/AIChat.razor @@ -13,230 +13,272 @@ @using Microsoft.AspNetCore.Components.Web @inject CompanyService companyService @inject ToastService toastService +گفتگو با دستیار هوشمند @CompanyInfo.FullName
-
- @if (isReady) - { - @if (isLogin) - { -
-
-

دستیار هوشمند @CompanyInfo.FullName

+
+ @if (isReady) + { +
+
+

دستیار هوشمند @CompanyInfo.FullName

-
+
- -
-
-
- @if (messages is not null) - { -
- @foreach (var msg in messages) - { -
-
- @msg.requestText -
-
+ +
+
+
+ @if (messages is not null) + { +
+ @foreach (var msg in messages) + { +
+
+ @msg.requestText +
+
-
-
- @msg.responseText -
-
+
+
+ @msg.responseText +
+
- } -
- } -
-
-
- - -
-
-
- } - else - { -
-
- -

نیاز به ورود دارید

-
-
- } - } - else - { -
-
- -

در حال بارگذاری ...

-
-
- } -
+ } +
+ } +
+
+
+ + +
+
+
+ + } + else + { +
+
+ @if (isError) + { +

@msgError

+ } + else + { + +

در حال بارگذاری ...

+ } + +
+
+ } +
@code { - ReadANDUpdate_CompanyDto? CompanyInfo = new(); - [Parameter] public int CompanyID { get; set; } - List messages = new(); - string inputText = string.Empty; - bool isLogin = false; - bool isReady = false; - public string aikeyUser { get; set; } - protected override async Task OnInitializedAsync() - { - await EnsureAuth(); - if (isLogin) - { - CompanyInfo = await companyService.GetCompany(CompanyID); - if (CompanyInfo != null) - await LoadMessages(); - else - { - toastService.Notify(new ToastMessage(ToastType.Danger, "شناسه شرکت صحیح نمی باشد")); + ReadANDUpdate_CompanyDto? CompanyInfo = new(); + [Parameter] public int CompanyID { get; set; } + List messages = new(); + string inputText = string.Empty; + bool isReady = false; + bool isError= false; + string msgError= ""; + public string aikeyUser { get; set; } = ""; + protected override async Task OnInitializedAsync() + { + await EnsureAuth(); + + CompanyInfo = await companyService.GetCompany(CompanyID); + if (CompanyInfo != null && CompanyInfo.allowBot) + { + await LoadMessages(); + isReady = true; + } + else + { + isError = true; + msgError = "دستیار هوشمند یافت نشد"; + } + } - } - } - } + private async Task EnsureAuth() + { + aikeyUser = await localStorageService.GetItem("aikeyUser"); + if (string.IsNullOrEmpty(aikeyUser)) + { + aikeyUser = Guid.NewGuid().ToString(); + await localStorageService.SetItem("aikeyUser", aikeyUser); + } - private async Task EnsureAuth() - { - aikeyUser = await localStorageService.GetItem("aikeyUser"); - if (string.IsNullOrEmpty(aikeyUser)) - { - aikeyUser = Guid.NewGuid().ToString(); - await localStorageService.SetItem("aikeyUser", aikeyUser); - } - isLogin = true; - isReady = true; - } + } - private async Task LoadMessages() - { - messages = await conversationService.GetAiCurrentResponses(CompanyID,aikeyUser); - StateHasChanged(); - await JS.InvokeVoidAsync("scrollToBottom", "ai-chat"); - } + private async Task LoadMessages() + { + messages = await conversationService.GetAiCurrentResponses(CompanyID, aikeyUser); + StateHasChanged(); + await JS.InvokeVoidAsync("scrollToBottom", "ai-chat"); + } + bool disSend = false; + private async Task Send() + { + if (string.IsNullOrWhiteSpace(inputText)) return; + disSend = true; + var dto = new aiNewResponseDto { companyId = CompanyID, aiKeyUser = aikeyUser, requestText = inputText }; + var okmodel = await conversationService.AiNewResponse(dto); + if (okmodel != null) + { + messages.Add(okmodel); + inputText = string.Empty; + StateHasChanged(); + await JS.InvokeVoidAsync("scrollToBottom", "ai-chat"); + } + else + { + toastService.Notify(new ToastMessage(ToastType.Danger, "ارسال ناموفق بود")); + } + disSend = false; + } - private async Task Send() - { - if (string.IsNullOrWhiteSpace(inputText)) return; + private async Task HandleKeyDown(KeyboardEventArgs e) + { + if (e.Key == "Enter") await Send(); + } - var dto = new aiNewResponseDto { companyId = CompanyID, aiKeyUser = aikeyUser, requestText = inputText }; - var okmodel = await conversationService.AiNewResponse(dto); - if (okmodel!=null) - { - messages.Add(okmodel); - inputText = string.Empty; - StateHasChanged(); - await JS.InvokeVoidAsync("scrollToBottom", "ai-chat"); - } - else - { - toastService.Notify(new ToastMessage(ToastType.Danger, "ارسال ناموفق بود")); - } - } - - private async Task HandleKeyDown(KeyboardEventArgs e) - { - if (e.Key == "Enter") await Send(); - } - - private async Task Logout() - { - await localStorageService.RemoveItem("aiKeyUser"); - isLogin = false; - } + private async Task Logout() + { + await localStorageService.RemoveItem("aiKeyUser"); + } } @@ -249,31 +291,31 @@ }; - \ No newline at end of file + \ No newline at end of file