diff --git a/.version b/.version index 4e31daf..68fa584 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -1.3.2.1 \ No newline at end of file +1.4.1.1 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e847d20..f7885e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base ENV ASPNETCORE_URLS=http://0.0.0.0:8010 WORKDIR /app EXPOSE 8010 -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["DocuMed.Api/DocuMed.Api.csproj", "DocuMed.Api/"] RUN dotnet restore "DocuMed.Api/DocuMed.Api.csproj" diff --git a/DocuMed.Api/Controllers/AiController.cs b/DocuMed.Api/Controllers/AiController.cs new file mode 100644 index 0000000..b2cbd62 --- /dev/null +++ b/DocuMed.Api/Controllers/AiController.cs @@ -0,0 +1,26 @@ +namespace DocuMed.Api.Controllers; + +public class AiController : ICarterModule +{ + public void AddRoutes(IEndpointRouteBuilder app) + { + var group = app.NewVersionedApi("Ai").MapGroup("api/ai"); + group.MapPost("chat", ChatAsync) + .WithDisplayName("AiChatBot") + .HasApiVersion(1.0); + } + + private async Task ChatAsync([FromBody] MetisMessage message, [FromServices] IRestApiWrapper apiWrapper, CancellationToken cancellationToken) + { + var messageRequest = new MetisMessageRequest + { + message = new MetisMessage + { + content = message.content + } + }; + var response = await apiWrapper.MetisRestApi.SendMessage("7324c5a0-5cad-4239-a8d9-38d99d490493", messageRequest, "tpsg-epC8BoLfa7uSL4ogjlocFLKiW7Un66e"); + return TypedResults.Ok(response.Content); + } + +} \ No newline at end of file diff --git a/DocuMed.Api/Controllers/HospitalController.cs b/DocuMed.Api/Controllers/HospitalController.cs index 7f319f1..91b336f 100644 --- a/DocuMed.Api/Controllers/HospitalController.cs +++ b/DocuMed.Api/Controllers/HospitalController.cs @@ -1,8 +1,4 @@ -using DocuMed.Domain.CommandQueries.Commands; -using DocuMed.Domain.Entities.MedicalHistory; -using MediatR; - -namespace DocuMed.Api.Controllers; +namespace DocuMed.Api.Controllers; public class HospitalController : ICarterModule { @@ -12,6 +8,9 @@ public class HospitalController : ICarterModule .MapGroup("api/hospital") .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser()); + group.MapGet("{id}/section", GetSectionsAsync) + .WithDisplayName("Get All Sections") + .HasApiVersion(1.0); group.MapGet("", GetAllAsync) .WithDisplayName("GetAll") @@ -31,37 +30,31 @@ public class HospitalController : ICarterModule .HasApiVersion(1.0); } - // GET:Get All Entity - private async Task GetAllAsync([FromQuery] int page, IMedicalHistoryRepository repository, CancellationToken cancellationToken) + // GET:Get All Sections + public virtual async Task GetSectionsAsync(Guid id, IRepositoryWrapper repositoryWrapper, ICurrentUserService currentUserService, CancellationToken cancellationToken) { - return TypedResults.Ok(await repository.GetMedicalHistoriesAsync(page, cancellationToken)); + return TypedResults.Ok(await repositoryWrapper.SetRepository
().TableNoTracking + .Where(s => s.HospitalId == id) + .Select(SectionMapper.ProjectToSDto).ToListAsync(cancellationToken)); } + // GET:Get All Entity + private async Task GetAllAsync([FromQuery] int page, IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(new GetHospitalsQuery(page), cancellationToken)); + // GET:Get An Entity By Id - private async Task GetAsync(Guid id, IMedicalHistoryRepository repository, CancellationToken cancellationToken) - { - - return TypedResults.Ok(await repository.GetMedicalHistoryAsync(id, cancellationToken)); - } + private async Task GetAsync(Guid id, IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(new GetHospitalQuery(id), cancellationToken)); // POST:Add New Entity private async Task Post([FromBody] CreateHospitalCommand dto, IMediator service, ICurrentUserService currentUserService, CancellationToken cancellationToken) - { - return TypedResults.Ok(await service.Send(dto,cancellationToken)); - } + => TypedResults.Ok(await service.Send(dto, cancellationToken)); // PUT:Update Entity private async Task Put([FromBody] UpdateHospitalCommand dto, IMediator service, ICurrentUserService currentUserService, CancellationToken cancellationToken) - { - return TypedResults.Ok(await service.Send(dto,cancellationToken)); - } + => TypedResults.Ok(await service.Send(dto, cancellationToken)); // DELETE:Delete Entity - private async Task Delete(Guid id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) - { - var ent = await repositoryWrapper.SetRepository().GetByIdAsync(cancellationToken, id); - repositoryWrapper.SetRepository().Delete(ent); - await repositoryWrapper.SaveChangesAsync(cancellationToken); - return TypedResults.Ok(); - } + private async Task Delete(Guid id, IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(new DeleteHospitalCommand(id), cancellationToken)); } \ No newline at end of file diff --git a/DocuMed.Api/Controllers/UniversityController.cs b/DocuMed.Api/Controllers/UniversityController.cs index 7518bb2..dd931e7 100644 --- a/DocuMed.Api/Controllers/UniversityController.cs +++ b/DocuMed.Api/Controllers/UniversityController.cs @@ -14,7 +14,7 @@ public class UniversityController : ICarterModule .WithDisplayName("Get All") .HasApiVersion(1.0); - group.MapGet("{id}/section", GetAllByUniversityAsync) + group.MapGet("{id}/hospital", GetHospitalsAsync) .WithDisplayName("Get All Sections") .HasApiVersion(1.0); @@ -34,25 +34,25 @@ public class UniversityController : ICarterModule // GET:Get All Sections - public virtual async Task GetAllByUniversityAsync(Guid id, IRepositoryWrapper repositoryWrapper, ICurrentUserService currentUserService, CancellationToken cancellationToken) + private async Task GetHospitalsAsync(Guid id, IRepositoryWrapper repositoryWrapper, ICurrentUserService currentUserService, CancellationToken cancellationToken) { - return TypedResults.Ok(await repositoryWrapper.SetRepository
().TableNoTracking - .Where(s => s.HospitalId == id) - .Select(SectionMapper.ProjectToSDto).ToListAsync(cancellationToken)); + return TypedResults.Ok(await repositoryWrapper.SetRepository().TableNoTracking + .Where(s => s.UniversityId == id) + .Select(HospitalMapper.ProjectToSDto).ToListAsync(cancellationToken)); } // GET:Get All Entity - public virtual async Task GetAllAsync([FromQuery] int page, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + private async Task GetAllAsync([FromQuery] int page, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) => TypedResults.Ok(await repositoryWrapper.SetRepository() .TableNoTracking .Select(UniversityMapper.ProjectToSDto).ToListAsync(cancellationToken)); // GET:Get An Entity By Id - public async Task GetAsync(int id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + private async Task GetAsync(int id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) => TypedResults.Ok(await repositoryWrapper.SetRepository().GetByIdAsync(cancellationToken, id)); // POST:Add New Entity - public virtual async Task Post([FromBody] UniversitySDto dto, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + private async Task Post([FromBody] UniversitySDto dto, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) { var ent = University.Create(dto.Name,dto.Address,dto.CityId); repositoryWrapper.SetRepository().Add(ent); @@ -61,7 +61,7 @@ public class UniversityController : ICarterModule } // PUT:Update Entity - public virtual async Task Put([FromBody] UniversitySDto dto, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + private async Task Put([FromBody] UniversitySDto dto, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) { var ent = University.Create(dto.Name,dto.Address,dto.CityId); ent.Id = dto.Id; @@ -71,7 +71,7 @@ public class UniversityController : ICarterModule } // DELETE:Delete Entity - public virtual async Task Delete(int id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) + private async Task Delete(int id, IRepositoryWrapper repositoryWrapper, CancellationToken cancellationToken) { var ent = await repositoryWrapper.SetRepository().GetByIdAsync(cancellationToken, id); repositoryWrapper.SetRepository().Delete(ent); diff --git a/DocuMed.Api/DocuMed.Api.csproj b/DocuMed.Api/DocuMed.Api.csproj index 9aacdcc..28fe87b 100644 --- a/DocuMed.Api/DocuMed.Api.csproj +++ b/DocuMed.Api/DocuMed.Api.csproj @@ -6,8 +6,8 @@ enable Linux ..\docker-compose.dcproj - 1.3.2.1 - 1.3.2.1 + 1.4.1.1 + 1.4.1.1 @@ -74,10 +74,13 @@ + + + @@ -85,12 +88,15 @@ + + + diff --git a/DocuMed.Core/CoreServices/AccountService.cs b/DocuMed.Core/CoreServices/AccountService.cs index a4cacdb..4a6cf36 100644 --- a/DocuMed.Core/CoreServices/AccountService.cs +++ b/DocuMed.Core/CoreServices/AccountService.cs @@ -141,6 +141,8 @@ public class AccountService( if (section != null) { token.User.SectionName = section.Name; + token.User.SectionId = section.Id; + token.User.HospitalId = section.HospitalId; } } diff --git a/DocuMed.Core/EntityServices/UserService.cs b/DocuMed.Core/EntityServices/UserService.cs index 1cb4ecf..2b2c9f9 100644 --- a/DocuMed.Core/EntityServices/UserService.cs +++ b/DocuMed.Core/EntityServices/UserService.cs @@ -1,4 +1,6 @@ -namespace DocuMed.Core.EntityServices; +using DocuMed.Domain.Entities.Staffs; + +namespace DocuMed.Core.EntityServices; public class UserService( @@ -85,6 +87,23 @@ public class UserService( user.BirthDate = request.BirthDate; user.Gender = request.Gender; + switch (request.ProfileType) + { + case ProfileType.Student: + var student = await repositoryWrapper.SetRepository().TableNoTracking + .FirstOrDefaultAsync(s => s.UserId == user.Id, cancellationToken); + if (student == null) + throw new AppException("Student not found", ApiResultStatusCode.NotFound); + student.SetSection(request.SectionId); + repositoryWrapper.SetRepository().Update(student); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + break; + case ProfileType.Patient: + break; + default: + throw new ArgumentOutOfRangeException(); + } + var result = await userManager.UpdateAsync(user); if (!result.Succeeded) throw new AppException(string.Join('|', result.Errors)); diff --git a/DocuMed.Domain/Dtos/RequestDtos/UserActionRequestDto.cs b/DocuMed.Domain/Dtos/RequestDtos/UserActionRequestDto.cs index 6a1ac42..d2b220f 100644 --- a/DocuMed.Domain/Dtos/RequestDtos/UserActionRequestDto.cs +++ b/DocuMed.Domain/Dtos/RequestDtos/UserActionRequestDto.cs @@ -13,4 +13,6 @@ public class UserActionRequestDto public string RoleName { get; set; } = string.Empty; public Guid UniversityId { get; set; } public Guid SectionId { get; set; } + public Guid HospitalId { get; set; } + public ProfileType ProfileType { get; set; } } \ No newline at end of file diff --git a/DocuMed.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs b/DocuMed.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs index 82b254f..63e5ecf 100644 --- a/DocuMed.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs +++ b/DocuMed.Domain/Dtos/SmallDtos/ApplicationUserSDto.cs @@ -14,6 +14,8 @@ public class ApplicationUserSDto : BaseDto public Gender Gender { get; set; } public SignUpStatus SignUpStatus { get; set; } public Guid UniversityId { get; set; } + public Guid HospitalId { get; set; } + public string HospitalName { get; set; } public Guid SectionId { get; set; } public string SectionName { get; set; } = string.Empty; diff --git a/DocuMed.Domain/Enums/ProfileType.cs b/DocuMed.Domain/Enums/ProfileType.cs new file mode 100644 index 0000000..22ab7ef --- /dev/null +++ b/DocuMed.Domain/Enums/ProfileType.cs @@ -0,0 +1,7 @@ +namespace DocuMed.Domain.Enums; + +public enum ProfileType +{ + Student, + Patient +} \ No newline at end of file diff --git a/DocuMed.Infrastructure/DocuMed.Infrastructure.csproj b/DocuMed.Infrastructure/DocuMed.Infrastructure.csproj index 5f02057..f8ff7d6 100644 --- a/DocuMed.Infrastructure/DocuMed.Infrastructure.csproj +++ b/DocuMed.Infrastructure/DocuMed.Infrastructure.csproj @@ -8,6 +8,7 @@ + @@ -27,6 +28,7 @@ + diff --git a/DocuMed.Infrastructure/Models/Metis/CreateSessionRequestDto.cs b/DocuMed.Infrastructure/Models/Metis/CreateSessionRequestDto.cs new file mode 100644 index 0000000..47a28b5 --- /dev/null +++ b/DocuMed.Infrastructure/Models/Metis/CreateSessionRequestDto.cs @@ -0,0 +1,9 @@ +namespace DocuMed.Infrastructure.Models.Metis; + +public class CreateSessionRequestDto +{ + public string BotId { get; set; } = string.Empty; + public MetisUser? User { get; set; } + public MetisMessageRequest? InitializeMessage { get; set; } +} + diff --git a/DocuMed.Infrastructure/Models/Metis/MetisMessageRequest.cs b/DocuMed.Infrastructure/Models/Metis/MetisMessageRequest.cs new file mode 100644 index 0000000..153c9e7 --- /dev/null +++ b/DocuMed.Infrastructure/Models/Metis/MetisMessageRequest.cs @@ -0,0 +1,12 @@ +namespace DocuMed.Infrastructure.Models.Metis; + +public class MetisMessageRequest +{ + public MetisMessage message { get; set; } = new(); +} + +public class MetisMessage +{ + public string content { get; set; } = string.Empty; + public string type { get; set; } = "USER"; +} \ No newline at end of file diff --git a/DocuMed.Infrastructure/Models/Metis/MetisMessageResponse.cs b/DocuMed.Infrastructure/Models/Metis/MetisMessageResponse.cs new file mode 100644 index 0000000..fbc47a0 --- /dev/null +++ b/DocuMed.Infrastructure/Models/Metis/MetisMessageResponse.cs @@ -0,0 +1,13 @@ +namespace DocuMed.Infrastructure.Models.Metis; + +public class MetisMessageResponse +{ + public string Id { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public object Attachments { get; set; } = string.Empty; + public long Timestamp { get; set; } + public string FinishReason { get; set; } = string.Empty; + public object Citations { get; set; } = string.Empty; + public object ToolCalls { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/DocuMed.Infrastructure/Models/Metis/MetisUser.cs b/DocuMed.Infrastructure/Models/Metis/MetisUser.cs new file mode 100644 index 0000000..5951bd4 --- /dev/null +++ b/DocuMed.Infrastructure/Models/Metis/MetisUser.cs @@ -0,0 +1,7 @@ +namespace DocuMed.Infrastructure.Models.Metis; + +public class MetisUser +{ + public string Name { get; set; } = string.Empty; + public string Id { get; set; } = Guid.NewGuid().ToString("N").Substring(0, 8); +} \ No newline at end of file diff --git a/DocuMed.Infrastructure/RestServices/IMetisRestApi.cs b/DocuMed.Infrastructure/RestServices/IMetisRestApi.cs new file mode 100644 index 0000000..ca389e9 --- /dev/null +++ b/DocuMed.Infrastructure/RestServices/IMetisRestApi.cs @@ -0,0 +1,13 @@ +namespace DocuMed.Infrastructure.RestServices; + +public interface IMetisRestApi +{ + [Post("/chat/session")] + public Task CreateSession(string sessionId, [Body] CreateSessionRequestDto request, [Header("x-api-key")] string metisToken); + + [Post("/chat/session/{sessionId}/message")] + public Task SendMessage(string sessionId, [Body] MetisMessageRequest request, [Header("x-api-key")] string metisToken); + + [Post("/chat/session/{sessionId}/message/stream")] + public Task SendStreamMessage(string sessionId, [Body] MetisMessageRequest request, [Header("x-api-key")] string metisToken); +} \ No newline at end of file diff --git a/DocuMed.Infrastructure/RestServices/RestApiWrapper.cs b/DocuMed.Infrastructure/RestServices/RestApiWrapper.cs index e55ceae..4218901 100644 --- a/DocuMed.Infrastructure/RestServices/RestApiWrapper.cs +++ b/DocuMed.Infrastructure/RestServices/RestApiWrapper.cs @@ -1,11 +1,21 @@ -namespace DocuMed.Infrastructure.RestServices; +using Newtonsoft.Json; + +namespace DocuMed.Infrastructure.RestServices; public interface IRestApiWrapper : IScopedDependency { IKaveNegarRestApi KaveNegarRestApi { get; } + + IMetisRestApi MetisRestApi { get; } } public class RestApiWrapper : IRestApiWrapper { + private static readonly RefitSettings setting = new RefitSettings(new NewtonsoftJsonContentSerializer(new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + + })); public IKaveNegarRestApi KaveNegarRestApi => RestService.For(RestAddress.BaseKaveNegar); + public IMetisRestApi MetisRestApi => RestService.For("https://api.metisai.ir/api/v1", setting); } \ No newline at end of file diff --git a/DocuMed.PWA/DocuMed.PWA.csproj b/DocuMed.PWA/DocuMed.PWA.csproj index c2eaada..99cf181 100644 --- a/DocuMed.PWA/DocuMed.PWA.csproj +++ b/DocuMed.PWA/DocuMed.PWA.csproj @@ -7,8 +7,8 @@ enable enable service-worker-assets.js - 1.3.2.1 - 1.3.2.1 + 1.4.1.1 + 1.4.1.1 diff --git a/DocuMed.PWA/Models/Address.cs b/DocuMed.PWA/Models/Address.cs index 7f53765..580454a 100644 --- a/DocuMed.PWA/Models/Address.cs +++ b/DocuMed.PWA/Models/Address.cs @@ -8,12 +8,15 @@ public static class Address #else public static string BaseAddress = "https://api.documed.ir/api"; #endif + public static string AuthController = $"{BaseAddress}/auth"; public static string CityController = $"{BaseAddress}/city"; public static string UniversityController = $"{BaseAddress}/university"; + public static string AiController = $"{BaseAddress}/ai"; public static string SectionController = $"{BaseAddress}/section"; public static string UserController = $"{BaseAddress}/user"; public static string MedicalHistoryTemplateController = $"{BaseAddress}/medicalhistory/template"; public static string MedicalHistoryController = $"{BaseAddress}/medicalhistory"; public static string PatientController = $"{BaseAddress}/patient"; + public static string HospitalController = $"{BaseAddress}/hospital"; } \ No newline at end of file diff --git a/DocuMed.PWA/Pages/MedicalHistoryActionParts/MedicalHistoryActionStep1.razor b/DocuMed.PWA/Pages/MedicalHistoryActionParts/MedicalHistoryActionStep1.razor index 0add5f8..0a05648 100644 --- a/DocuMed.PWA/Pages/MedicalHistoryActionParts/MedicalHistoryActionStep1.razor +++ b/DocuMed.PWA/Pages/MedicalHistoryActionParts/MedicalHistoryActionStep1.razor @@ -221,7 +221,7 @@ { var token = await UserUtility.GetBearerTokenAsync(); var user = await UserUtility.GetUserAsync(); - Sections = await RestWrapper.SectionRestApi.GetByUniversityAsync(user.UniversityId, token); + Sections = await RestWrapper.HospitalRestApi.GetSectionsAsync(user.HospitalId, token); if (section.IsNullOrEmpty()) return Sections; diff --git a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionPage.razor b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionPage.razor index e61b57a..43de5e1 100644 --- a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionPage.razor +++ b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionPage.razor @@ -27,17 +27,17 @@
- +
- +
- +
diff --git a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep1.razor b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep1.razor index 8af6ac7..df1b2aa 100644 --- a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep1.razor +++ b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep1.razor @@ -66,7 +66,7 @@ { var token = await UserUtility.GetBearerTokenAsync(); var user = await UserUtility.GetUserAsync(); - Sections = await RestWrapper.SectionRestApi.GetByUniversityAsync(user.UniversityId, token); + Sections = await RestWrapper.HospitalRestApi.GetSectionsAsync(user.HospitalId, token); if (section.IsNullOrEmpty()) return Sections; diff --git a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep2.razor b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep2.razor index e62078e..05081ce 100644 --- a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep2.razor +++ b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep2.razor @@ -1,4 +1,5 @@ -@using DocuMed.Domain.Entities.MedicalHistoryTemplate +@inject IRestWrapper RestWrapper +@inject ISnackbar Snackbar @@ -19,24 +20,47 @@ Variant="Variant.Outlined" /> @* *@ - + class="font-extrabold text-lg right-0 rounded-md py-3 bg-[--color-medicalhistory] text-gray-800"> + افزودن + @AiResponse + + + + @if (IsProcessing) + { +

+ در حال فکر کردن .... +

+ } +
+@* focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:ring-offset-slate-50 *@ @code { private MedicalHistoryQuestionType _questionType; private string _questionTitle = string.Empty; private MedicalHistoryPart _questionPart = MedicalHistoryPart.PresentIllness; + private bool IsProcessing { get; set; } + private MarkupString AiResponse { get; set; } [Parameter] public List PiQuestions { get; set; } = new(); + [Parameter] + public string ChiefComplaint { get; set; } = string.Empty; + private void RemoveQuestion(MedicalHistoryQuestionSDto question) { PiQuestions.Remove(question); @@ -44,11 +68,43 @@ private void AddQuestion() { PiQuestions.Add(new MedicalHistoryQuestionSDto - { - Part = _questionPart, - Question = _questionTitle, - QuestionType = _questionType - }); + { + Part = _questionPart, + Question = _questionTitle, + QuestionType = _questionType + }); _questionTitle = string.Empty; } + + private async Task AskWithAi() + { + + try + { + IsProcessing = true; + var request = new + { + content = $"شکایت اصلی بیمار (CC) {ChiefComplaint} است. لطفاً سوالات بخش PI (Present Illness) را در سه دسته زیر تولید کنید: سوالات توضیحی (Open-ended): سوالاتی که بیمار باید توضیح دهد. سوالات بله/خیر (Yes/No): سوالاتی که پاسخ مشخص بله یا خیر دارند. سوالات زمانی (Time-based): سوالاتی مرتبط با زمان شروع، مدت و تغییرات مشکل. لطفاً سوالات مرتبط با سردرد شامل شدت، محل، عوامل تشدیدکننده یا تسکین‌دهنده و علائم همراه (مثل تهوع یا تاری دید) باشد. use html concept for response and just send html and remove , head , html and body tag" + }; + var response = await RestWrapper.AiRestApi.ChatAsync(request); + response = response.Replace("```html", null); + response = response.Replace("```", null); + response = response.Replace("\\n", null); + response = response.Replace(@"""", null); + AiResponse = (MarkupString)response; + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + Snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + } + catch (Exception e) + { + Snackbar.Add(e.Message, Severity.Error); + } + finally + { + IsProcessing = false; + } + } } \ No newline at end of file diff --git a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep3.razor b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep3.razor index ab373f0..81aac9d 100644 --- a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep3.razor +++ b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep3.razor @@ -1,5 +1,5 @@ -@using DocuMed.Domain.Entities.MedicalHistoryTemplate - +@inject IRestWrapper RestWrapper +@inject ISnackbar Snackbar @@ -22,6 +22,25 @@ + افزودن + @AiPMHResponse + + + + @if (IsProcessing) + { +

+ در حال فکر کردن .... +

+ } + + @foreach (var item in PshQuestions) @@ -50,6 +69,23 @@ + افزودن + @AiPSHResponse + + + + @if (IsProcessing) + { +

+ در حال فکر کردن .... +

+ }
@@ -60,11 +96,12 @@ [Parameter] public List PshQuestions { get; set; } = new(); - private string _pdhQuestionTitle = string.Empty; private MedicalHistoryQuestionType _pdhQuestionType; private string _pshQuestionTitle = string.Empty; private MedicalHistoryQuestionType _pshQuestionType; + [Parameter] + public string ChiefComplaint { get; set; } = string.Empty; private void RemovePiQuestion(MedicalHistoryQuestionSDto question) { @@ -96,4 +133,72 @@ _pshQuestionTitle = string.Empty; } + + private bool IsProcessing { get; set; } + private MarkupString AiPMHResponse { get; set; } + private async Task AskPMHWithAi() + { + + try + { + IsProcessing = true; + var request = new + { + content = $"شکایت اصلی بیمار (CC) {ChiefComplaint} است. لطفاً سوالات بخش تاریخچه بیماری قبلی ( Past Medical History ) را در سه دسته زیر تولید کنید: سوالات توضیحی (Open-ended): سوالاتی که بیمار باید توضیح دهد. سوالات بله/خیر (Yes/No): سوالاتی که پاسخ مشخص بله یا خیر دارند. سوالات زمانی (Time-based): سوالاتی مرتبط با زمان شروع، مدت و تغییرات مشکل. لطفاً سوالات مرتبط با سردرد شامل شدت، محل، عوامل تشدیدکننده یا تسکین‌دهنده و علائم همراه (مثل تهوع یا تاری دید) باشد. use html concept for response and just send html and remove , head , html and body tag" + }; + var response = await RestWrapper.AiRestApi.ChatAsync(request); + response = response.Replace("```html", null); + response = response.Replace("```", null); + response = response.Replace("\\n", null); + response = response.Replace(@"""", null); + AiPMHResponse = (MarkupString)response; + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + Snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + } + catch (Exception e) + { + Snackbar.Add(e.Message, Severity.Error); + } + finally + { + IsProcessing = false; + } + } + + + private MarkupString AiPSHResponse { get; set; } + private async Task AskPSHWithAi() + { + + try + { + IsProcessing = true; + var request = new + { + content = $"شکایت اصلی بیمار (CC) {ChiefComplaint} است. لطفاً سوالات بخش تاریخچه جراحی های قبلی ( Past Surgery History ) را در سه دسته زیر تولید کنید: سوالات توضیحی (Open-ended): سوالاتی که بیمار باید توضیح دهد. سوالات بله/خیر (Yes/No): سوالاتی که پاسخ مشخص بله یا خیر دارند. سوالات زمانی (Time-based): سوالاتی مرتبط با زمان شروع، مدت و تغییرات مشکل. لطفاً سوالات مرتبط با سردرد شامل شدت، محل، عوامل تشدیدکننده یا تسکین‌دهنده و علائم همراه (مثل تهوع یا تاری دید) باشد. use html concept for response and just send html and remove , head , html and body tag" + }; + var response = await RestWrapper.AiRestApi.ChatAsync(request); + response = response.Replace("```html", null); + response = response.Replace("```", null); + response = response.Replace("\\n", null); + response = response.Replace(@"""", null); + AiPSHResponse = (MarkupString)response; + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + Snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + } + catch (Exception e) + { + Snackbar.Add(e.Message, Severity.Error); + } + finally + { + IsProcessing = false; + } + } } \ No newline at end of file diff --git a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep4.razor b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep4.razor index a829250..b537a43 100644 --- a/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep4.razor +++ b/DocuMed.PWA/Pages/MedicalHistoryTemplateActionParts/MedicalHistoryTemplateActionStep4.razor @@ -1,4 +1,5 @@ -@using DocuMed.Domain.Entities.MedicalHistoryTemplate +@inject IRestWrapper RestWrapper +@inject ISnackbar Snackbar @@ -22,6 +23,23 @@ + افزودن + @AiResponse + + + + @if (IsProcessing) + { +

+ در حال فکر کردن .... +

+ } @@ -66,9 +84,14 @@ -
-@code { +@code +{ + private bool IsProcessing { get; set; } + private MarkupString AiResponse { get; set; } + [Parameter] + public string ChiefComplaint { get; set; } = string.Empty; + private string _familyHistoryQuestionTitle = string.Empty; private MedicalHistoryQuestionType _familyHistoryQuestionType; [Parameter] @@ -125,4 +148,37 @@ }); _hhName = string.Empty; } + + + private async Task AskWithAi() + { + + try + { + IsProcessing = true; + var request = new + { + content = $"شکایت اصلی بیمار (CC) {ChiefComplaint} است. لطفاً سوالات بخش PI (Present Illness) را در سه دسته زیر تولید کنید: سوالات توضیحی (Open-ended): سوالاتی که بیمار باید توضیح دهد. سوالات بله/خیر (Yes/No): سوالاتی که پاسخ مشخص بله یا خیر دارند. سوالات زمانی (Time-based): سوالاتی مرتبط با زمان شروع، مدت و تغییرات مشکل. لطفاً سوالات مرتبط با سردرد شامل شدت، محل، عوامل تشدیدکننده یا تسکین‌دهنده و علائم همراه (مثل تهوع یا تاری دید) باشد. use html concept for response and just send html and remove , head , html and body tag" + }; + var response = await RestWrapper.AiRestApi.ChatAsync(request); + response = response.Replace("```html", null); + response = response.Replace("```", null); + response = response.Replace("\\n", null); + response = response.Replace(@"""", null); + AiResponse = (MarkupString)response; + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + Snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + } + catch (Exception e) + { + Snackbar.Add(e.Message, Severity.Error); + } + finally + { + IsProcessing = false; + } + } } \ No newline at end of file diff --git a/DocuMed.PWA/Pages/ProfilePage.razor b/DocuMed.PWA/Pages/ProfilePage.razor index 744cd2e..ceb651e 100644 --- a/DocuMed.PWA/Pages/ProfilePage.razor +++ b/DocuMed.PWA/Pages/ProfilePage.razor @@ -52,6 +52,25 @@ + + + + +
+ +

منتظر بمانید

+
+
+
+
+ +

@e.Name

+
+
+ Cities { get; private set; } = new List(); public List Universities { get; private set; } = new List(); + public List Hospitals { get; private set; } = new List(); public List Sections { get; private set; } = new List(); public SectionSDto? SelectedSection { get; set; } public CitySDto? SelectedCity { get; set; } public UniversitySDto? SelectedUni { get; set; } + public HospitalSDto? SelectedHospital { get; set; } public readonly string Version = Assembly.GetAssembly(typeof(Program))?.GetName()?.Version?.ToString() ?? string.Empty; @@ -37,12 +40,14 @@ public class ProfilePageViewModel( { SelectedUni = Universities.FirstOrDefault(u => u.Id == User.UniversityId); SelectedCity = Cities.FirstOrDefault(c => c.Id == SelectedUni?.CityId); - if (SelectedUni != null) + if (SelectedHospital != null) + { if (User.SectionId != Guid.Empty) { - Sections = await RestWrapper.SectionRestApi.GetByUniversityAsync(SelectedUni.Id, token); + Sections = await RestWrapper.HospitalRestApi.GetSectionsAsync(SelectedHospital.Id, token); SelectedSection = Sections.FirstOrDefault(s => s.Id == User.SectionId); } + } } await base.InitializeAsync(); @@ -55,6 +60,7 @@ public class ProfilePageViewModel( IsProcessing = true; var token = await UserUtility.GetBearerTokenAsync(); var request = User.Adapt(); + request.ProfileType = ProfileType.Student; if (SelectedUni != null) { request.UniversityId = SelectedUni.Id; @@ -66,6 +72,13 @@ public class ProfilePageViewModel( User.SectionId = SelectedSection.Id; User.SectionName = SelectedSection.Name; } + + if (SelectedHospital != null) + { + request.HospitalId = SelectedHospital.Id; + User.HospitalId = SelectedHospital.Id; + User.HospitalName = SelectedHospital.Name; + } await RestWrapper.UserRestApi.UpdateUserAsync(request, token); await UserUtility.SetUserAsync(User); Snackbar.Add("ویرایش حساب کاربری با موفقیت انجام شد", Severity.Success); @@ -92,8 +105,6 @@ public class ProfilePageViewModel( navigationManager.NavigateTo(""); } - - public async Task> SearchCity(string city) { try @@ -123,10 +134,10 @@ public class ProfilePageViewModel( { try { - if (SelectedUni != null) + if (SelectedHospital != null) { var token = await UserUtility.GetBearerTokenAsync(); - Sections = await RestWrapper.SectionRestApi.GetByUniversityAsync(SelectedUni.Id, token); + Sections = await RestWrapper.HospitalRestApi.GetSectionsAsync(SelectedHospital.Id, token); } if (section.IsNullOrEmpty()) return Sections; @@ -169,4 +180,29 @@ public class ProfilePageViewModel( return Universities; } } + public async Task> SearchHospital(string hospital) + { + try + { + if (SelectedUni != null) + { + var token = await UserUtility.GetBearerTokenAsync(); + Hospitals = await RestWrapper.UniversityRestApi.GetHospitalsAsync(SelectedUni.Id, token); + } + if (hospital.IsNullOrEmpty()) + return Hospitals; + return Hospitals.Where(c => c.Name.Contains(hospital)); + } + catch (ApiException ex) + { + var exe = await ex.GetContentAsAsync(); + Snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error); + return Hospitals; + } + catch (Exception e) + { + Snackbar.Add(e.Message, Severity.Error); + return Hospitals; + } + } } \ No newline at end of file diff --git a/DocuMed.PWA/Services/RestServices/IAiRestApi.cs b/DocuMed.PWA/Services/RestServices/IAiRestApi.cs new file mode 100644 index 0000000..3f64a3d --- /dev/null +++ b/DocuMed.PWA/Services/RestServices/IAiRestApi.cs @@ -0,0 +1,7 @@ +namespace DocuMed.PWA.Services.RestServices; + +public interface IAiRestApi +{ + [Post("/chat")] + public Task ChatAsync([Body]object request); +} \ No newline at end of file diff --git a/DocuMed.PWA/Services/RestServices/IHospitalRestApi.cs b/DocuMed.PWA/Services/RestServices/IHospitalRestApi.cs new file mode 100644 index 0000000..2a50820 --- /dev/null +++ b/DocuMed.PWA/Services/RestServices/IHospitalRestApi.cs @@ -0,0 +1,7 @@ +namespace DocuMed.PWA.Services.RestServices; + +public interface IHospitalRestApi +{ + [Get("/{hospitalId}/section")] + Task> GetSectionsAsync(Guid hospitalId, [Header("Authorization")] string authorization); +} \ No newline at end of file diff --git a/DocuMed.PWA/Services/RestServices/IRestWrapper.cs b/DocuMed.PWA/Services/RestServices/IRestWrapper.cs index 2d19e8e..d90ceb5 100644 --- a/DocuMed.PWA/Services/RestServices/IRestWrapper.cs +++ b/DocuMed.PWA/Services/RestServices/IRestWrapper.cs @@ -12,4 +12,7 @@ public interface IRestWrapper public IUserRestApi UserRestApi { get; } public IMedicalHistoryRestApi MedicalHistoryRestApi { get; } public IPatientRestApi PatientRestApi { get; } + public IHospitalRestApi HospitalRestApi { get; } + public IUniversityRestApi UniversityRestApi { get; } + public IAiRestApi AiRestApi { get; } } \ No newline at end of file diff --git a/DocuMed.PWA/Services/RestServices/ISectionRestApi.cs b/DocuMed.PWA/Services/RestServices/ISectionRestApi.cs index b88fcfd..933ec44 100644 --- a/DocuMed.PWA/Services/RestServices/ISectionRestApi.cs +++ b/DocuMed.PWA/Services/RestServices/ISectionRestApi.cs @@ -4,6 +4,4 @@ namespace DocuMed.PWA.Services.RestServices; public interface ISectionRestApi : ICrudDtoApiRest { - [Get("/university/{universityId}")] - Task> GetByUniversityAsync(Guid universityId, [Header("Authorization")] string authorization); } \ No newline at end of file diff --git a/DocuMed.PWA/Services/RestServices/IUniversityRestApi.cs b/DocuMed.PWA/Services/RestServices/IUniversityRestApi.cs new file mode 100644 index 0000000..2f6a949 --- /dev/null +++ b/DocuMed.PWA/Services/RestServices/IUniversityRestApi.cs @@ -0,0 +1,7 @@ +namespace DocuMed.PWA.Services.RestServices; + +public interface IUniversityRestApi +{ + [Get("/{universityId}/hospital")] + Task> GetHospitalsAsync(Guid universityId, [Header("Authorization")] string authorization); +} \ No newline at end of file diff --git a/DocuMed.PWA/Services/RestServices/RestWrapper.cs b/DocuMed.PWA/Services/RestServices/RestWrapper.cs index 2b0209e..7d8d404 100644 --- a/DocuMed.PWA/Services/RestServices/RestWrapper.cs +++ b/DocuMed.PWA/Services/RestServices/RestWrapper.cs @@ -26,4 +26,7 @@ public class RestWrapper : IRestWrapper public IUserRestApi UserRestApi => RestService.For(Address.UserController, setting); public IMedicalHistoryRestApi MedicalHistoryRestApi => RestService.For(Address.MedicalHistoryController); public IPatientRestApi PatientRestApi => RestService.For(Address.PatientController,setting); + public IHospitalRestApi HospitalRestApi => RestService.For(Address.HospitalController, setting); + public IUniversityRestApi UniversityRestApi => RestService.For(Address.UniversityController, setting); + public IAiRestApi AiRestApi => RestService.For(Address.AiController, setting); } \ No newline at end of file diff --git a/DocuMed.PWA/tailwind.config.js b/DocuMed.PWA/tailwind.config.js index ba45062..f16c571 100644 --- a/DocuMed.PWA/tailwind.config.js +++ b/DocuMed.PWA/tailwind.config.js @@ -20,6 +20,9 @@ module.exports = { '6xl': '4rem', '7xl': '5rem' }, + animation: { + 'gradient': 'gradient 8s linear infinite', + }, fontFamily: { "iranyekan": ["'iranyekan'"], }, diff --git a/DocuMed.PWA/wwwroot/css/app.min.css b/DocuMed.PWA/wwwroot/css/app.min.css index 38f5cd6..553eb45 100644 --- a/DocuMed.PWA/wwwroot/css/app.min.css +++ b/DocuMed.PWA/wwwroot/css/app.min.css @@ -518,6 +518,15 @@ video { .fixed { position: fixed; } +.absolute { + position: absolute; +} +.relative { + position: relative; +} +.inset-\[-1000\%\] { + inset: -1000%; +} .bottom-0 { bottom: 0px; } @@ -720,6 +729,9 @@ video { .mt-auto { margin-top: auto; } +.inline-block { + display: inline-block; +} .flex { display: flex; } @@ -853,6 +865,18 @@ video { .basis-full { flex-basis: 100%; } +@keyframes spin { + + to { + transform: rotate(360deg); + } +} +.animate-\[spin_2s_linear_infinite\] { + animation: spin 2s linear infinite; +} +.cursor-pointer { + cursor: pointer; +} .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } @@ -989,6 +1013,35 @@ video { .bg-opacity-20 { --tw-bg-opacity: 0.2; } +.bg-\[conic-gradient\(from_90deg_at_50\%_50\%\2c \#E2CBFF_0\%\2c \#393BB2_50\%\2c \#E2CBFF_100\%\)\] { + background-image: conic-gradient(from 90deg at 50% 50%,#E2CBFF 0%,#393BB2 50%,#E2CBFF 100%); +} +.bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); +} +.from-sky-600 { + --tw-gradient-from: #0284c7 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(2 132 199 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} +.via-blue-500 { + --tw-gradient-to: rgb(59 130 246 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #3b82f6 var(--tw-gradient-via-position), var(--tw-gradient-to); +} +.via-violet-500 { + --tw-gradient-to: rgb(139 92 246 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #8b5cf6 var(--tw-gradient-via-position), var(--tw-gradient-to); +} +.to-fuchsia-400 { + --tw-gradient-to: #e879f9 var(--tw-gradient-to-position); +} +.to-violet-400 { + --tw-gradient-to: #a78bfa var(--tw-gradient-to-position); +} +.bg-clip-text { + -webkit-background-clip: text; + background-clip: text; +} .p-0 { padding: 0px; } @@ -1004,6 +1057,9 @@ video { .p-5 { padding: 1.25rem; } +.p-\[1px\] { + padding: 1px; +} .px-1 { padding-left: 0.25rem; padding-right: 0.25rem; @@ -1133,6 +1189,10 @@ video { .text-\[--color-primary\] { color: var(--color-primary); } +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} .text-blue-500 { --tw-text-opacity: 1; color: rgb(59 130 246 / var(--tw-text-opacity)); @@ -1157,6 +1217,9 @@ video { --tw-text-opacity: 1; color: rgb(31 41 55 / var(--tw-text-opacity)); } +.text-transparent { + color: transparent; +} .text-white { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); @@ -1172,6 +1235,11 @@ video { } .filter { filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} +.backdrop-blur-3xl { + --tw-backdrop-blur: blur(64px); + -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); + backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); } @font-face { @@ -1359,11 +1427,35 @@ a, .btn-link { border-color: rgb(20 184 166 / var(--tw-border-opacity)); } + .focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + + .focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + + .focus\:ring-slate-400:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(148 163 184 / var(--tw-ring-opacity)); +} + .focus\:ring-teal-500:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(20 184 166 / var(--tw-ring-opacity)); } + .focus\:ring-offset-2:focus { + --tw-ring-offset-width: 2px; +} + + .focus\:ring-offset-slate-50:focus { + --tw-ring-offset-color: #f8fafc; +} + .group:hover .group-hover\:text-\[--color-primary\] { color: var(--color-primary); }