Compare commits

...

14 Commits

Author SHA1 Message Date
mmrbnjd
58975ce589 ... 2025-10-18 17:48:45 +03:30
mmrbnjd
337fbba488 ... 2025-10-12 23:34:24 +03:30
mmrbnjd
8a8239908f رفع مشکل لود نشدن فایل js برای pwa در index,html 2025-10-11 22:24:02 +03:30
mmrbnjd
d93c3dce8a ... 2025-10-10 22:41:27 +03:30
mmrbnjd
ba3bffd9eb docker 2025-10-03 23:30:50 +03:30
mmrbnjd
4e31d34164 .. 2025-09-05 20:39:23 +03:30
mmrbnjd
8b4e21f29a compose command 2025-09-04 19:09:55 +03:30
mmrbnjd
4748413951 docker compose 2025-09-04 19:08:57 +03:30
mmrbnjd
1ca1b4f1eb dockrfile wasm 2025-09-04 15:34:30 +03:30
mmrbnjd
2f66807bc1 app sett 2025-09-03 20:05:35 +03:30
mmrbnjd
869d1f2ef1 ... 2025-09-02 18:19:20 +03:30
mmrbnjd
c889ce9e03 ... 2025-09-01 19:46:37 +03:30
mmrbnjd
6a2de3d7c2 docker file in asa 2025-08-29 22:43:47 +03:30
mmrbnjd
49304f93a2 ... 2025-08-29 19:01:13 +03:30
36 changed files with 1136 additions and 326 deletions

30
.dockerignore Normal file
View File

@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@@ -7,14 +7,13 @@ package aia;
service aiAssistance {
rpc SendQuestion (aiaRequest) returns (aiaReply);
rpc imageAnalize (aiaRequest) returns (aiaReply);
}
message aiaRequest {
string question = 1;
string prompts = 2;
string model = 3;
string apitoken = 4;
string url = 5;
}
message aiaReply {

View File

@@ -23,6 +23,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HushianWebApp", "Presentati
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AIAss", "Presentation\AIAss\AIAss.csproj", "{AF6AE286-63CF-4A8A-A0B4-DBDA047FC9AE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
ProjectSection(SolutionItems) = preProject
command.txt = command.txt
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View File

@@ -39,7 +39,7 @@ namespace Hushian.Infrastructure.Ai_Ass
(new aiaRequest()
{ Prompts = Request.ToString(),
Question = Request.question,
Model=model,Apitoken=_aiSettings.apitoken,Url=_aiSettings.url });
Model=model});
if (response!=null)
{

View File

@@ -24,16 +24,16 @@ namespace AIAss.Protos {
static AiaReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"CglhaWEucHJvdG8SA2FpYSJdCgphaWFSZXF1ZXN0EhAKCHF1ZXN0aW9uGAEg",
"ASgJEg8KB3Byb21wdHMYAiABKAkSDQoFbW9kZWwYAyABKAkSEAoIYXBpdG9r",
"ZW4YBCABKAkSCwoDdXJsGAUgASgJIhsKCGFpYVJlcGx5Eg8KB21lc3NhZ2UY",
"ASABKAkyPgoMYWlBc3Npc3RhbmNlEi4KDFNlbmRRdWVzdGlvbhIPLmFpYS5h",
"aWFSZXF1ZXN0Gg0uYWlhLmFpYVJlcGx5Qg+qAgxBSUFzcy5Qcm90b3NiBnBy",
"b3RvMw=="));
"CglhaWEucHJvdG8SA2FpYSI+CgphaWFSZXF1ZXN0EhAKCHF1ZXN0aW9uGAEg",
"ASgJEg8KB3Byb21wdHMYAiABKAkSDQoFbW9kZWwYAyABKAkiGwoIYWlhUmVw",
"bHkSDwoHbWVzc2FnZRgBIAEoCTJuCgxhaUFzc2lzdGFuY2USLgoMU2VuZFF1",
"ZXN0aW9uEg8uYWlhLmFpYVJlcXVlc3QaDS5haWEuYWlhUmVwbHkSLgoMaW1h",
"Z2VBbmFsaXplEg8uYWlhLmFpYVJlcXVlc3QaDS5haWEuYWlhUmVwbHlCD6oC",
"DEFJQXNzLlByb3Rvc2IGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::AIAss.Protos.aiaRequest), global::AIAss.Protos.aiaRequest.Parser, new[]{ "Question", "Prompts", "Model", "Apitoken", "Url" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::AIAss.Protos.aiaRequest), global::AIAss.Protos.aiaRequest.Parser, new[]{ "Question", "Prompts", "Model" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::AIAss.Protos.aiaReply), global::AIAss.Protos.aiaReply.Parser, new[]{ "Message" }, null, null, null, null)
}));
}
@@ -79,8 +79,6 @@ namespace AIAss.Protos {
question_ = other.question_;
prompts_ = other.prompts_;
model_ = other.model_;
apitoken_ = other.apitoken_;
url_ = other.url_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
@@ -126,30 +124,6 @@ namespace AIAss.Protos {
}
}
/// <summary>Field number for the "apitoken" field.</summary>
public const int ApitokenFieldNumber = 4;
private string apitoken_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Apitoken {
get { return apitoken_; }
set {
apitoken_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "url" field.</summary>
public const int UrlFieldNumber = 5;
private string url_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Url {
get { return url_; }
set {
url_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
@@ -168,8 +142,6 @@ namespace AIAss.Protos {
if (Question != other.Question) return false;
if (Prompts != other.Prompts) return false;
if (Model != other.Model) return false;
if (Apitoken != other.Apitoken) return false;
if (Url != other.Url) return false;
return Equals(_unknownFields, other._unknownFields);
}
@@ -180,8 +152,6 @@ namespace AIAss.Protos {
if (Question.Length != 0) hash ^= Question.GetHashCode();
if (Prompts.Length != 0) hash ^= Prompts.GetHashCode();
if (Model.Length != 0) hash ^= Model.GetHashCode();
if (Apitoken.Length != 0) hash ^= Apitoken.GetHashCode();
if (Url.Length != 0) hash ^= Url.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
@@ -212,14 +182,6 @@ namespace AIAss.Protos {
output.WriteRawTag(26);
output.WriteString(Model);
}
if (Apitoken.Length != 0) {
output.WriteRawTag(34);
output.WriteString(Apitoken);
}
if (Url.Length != 0) {
output.WriteRawTag(42);
output.WriteString(Url);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
@@ -242,14 +204,6 @@ namespace AIAss.Protos {
output.WriteRawTag(26);
output.WriteString(Model);
}
if (Apitoken.Length != 0) {
output.WriteRawTag(34);
output.WriteString(Apitoken);
}
if (Url.Length != 0) {
output.WriteRawTag(42);
output.WriteString(Url);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
@@ -269,12 +223,6 @@ namespace AIAss.Protos {
if (Model.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Model);
}
if (Apitoken.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Apitoken);
}
if (Url.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Url);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
@@ -296,12 +244,6 @@ namespace AIAss.Protos {
if (other.Model.Length != 0) {
Model = other.Model;
}
if (other.Apitoken.Length != 0) {
Apitoken = other.Apitoken;
}
if (other.Url.Length != 0) {
Url = other.Url;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
@@ -329,14 +271,6 @@ namespace AIAss.Protos {
Model = input.ReadString();
break;
}
case 34: {
Apitoken = input.ReadString();
break;
}
case 42: {
Url = input.ReadString();
break;
}
}
}
#endif
@@ -364,14 +298,6 @@ namespace AIAss.Protos {
Model = input.ReadString();
break;
}
case 34: {
Apitoken = input.ReadString();
break;
}
case 42: {
Url = input.ReadString();
break;
}
}
}
}

View File

@@ -58,6 +58,14 @@ namespace AIAss.Protos {
__Marshaller_aia_aiaRequest,
__Marshaller_aia_aiaReply);
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
static readonly grpc::Method<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply> __Method_imageAnalize = new grpc::Method<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply>(
grpc::MethodType.Unary,
__ServiceName,
"imageAnalize",
__Marshaller_aia_aiaRequest,
__Marshaller_aia_aiaReply);
/// <summary>Service descriptor</summary>
public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
{
@@ -74,6 +82,12 @@ namespace AIAss.Protos {
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual global::System.Threading.Tasks.Task<global::AIAss.Protos.aiaReply> imageAnalize(global::AIAss.Protos.aiaRequest request, grpc::ServerCallContext context)
{
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
}
}
/// <summary>Client for aiAssistance</summary>
@@ -123,6 +137,26 @@ namespace AIAss.Protos {
{
return CallInvoker.AsyncUnaryCall(__Method_SendQuestion, null, options, request);
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual global::AIAss.Protos.aiaReply imageAnalize(global::AIAss.Protos.aiaRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
{
return imageAnalize(request, new grpc::CallOptions(headers, deadline, cancellationToken));
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual global::AIAss.Protos.aiaReply imageAnalize(global::AIAss.Protos.aiaRequest request, grpc::CallOptions options)
{
return CallInvoker.BlockingUnaryCall(__Method_imageAnalize, null, options, request);
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual grpc::AsyncUnaryCall<global::AIAss.Protos.aiaReply> imageAnalizeAsync(global::AIAss.Protos.aiaRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
{
return imageAnalizeAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken));
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual grpc::AsyncUnaryCall<global::AIAss.Protos.aiaReply> imageAnalizeAsync(global::AIAss.Protos.aiaRequest request, grpc::CallOptions options)
{
return CallInvoker.AsyncUnaryCall(__Method_imageAnalize, null, options, request);
}
/// <summary>Creates a new instance of client from given <c>ClientBaseConfiguration</c>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
protected override aiAssistanceClient NewInstance(ClientBaseConfiguration configuration)
@@ -137,7 +171,8 @@ namespace AIAss.Protos {
public static grpc::ServerServiceDefinition BindService(aiAssistanceBase serviceImpl)
{
return grpc::ServerServiceDefinition.CreateBuilder()
.AddMethod(__Method_SendQuestion, serviceImpl.SendQuestion).Build();
.AddMethod(__Method_SendQuestion, serviceImpl.SendQuestion)
.AddMethod(__Method_imageAnalize, serviceImpl.imageAnalize).Build();
}
/// <summary>Register service method with a service binder with or without implementation. Useful when customizing the service binding logic.
@@ -148,6 +183,7 @@ namespace AIAss.Protos {
public static void BindService(grpc::ServiceBinderBase serviceBinder, aiAssistanceBase serviceImpl)
{
serviceBinder.AddMethod(__Method_SendQuestion, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply>(serviceImpl.SendQuestion));
serviceBinder.AddMethod(__Method_imageAnalize, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply>(serviceImpl.imageAnalize));
}
}

View File

@@ -0,0 +1,374 @@
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these files will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment the next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
CConversionReport*.XML
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
# Docker
Dockerfile
.dockerignore
# Git
.git
.gitignore
# IDE
.vscode/
.idea/
*.user
*.suo
*.userosscache
*.sln.docstates

View File

@@ -0,0 +1,10 @@
 docker build -t ai_assistance .
docker run --name openai_assistance -d -p 5042:5010 ai_assistance
--------------------------------------------------------------------------------------------
in local => docker build -t mmrbnjd/ai_assistance:latest .
in local => docker push mmrbnjd/ai_assistance:latest
in server => docker pull mmrbnjd/ai_assistance:latest
in server => docker run -d -p 5042:5010 --restart always mmrbnjd/ai_assistance:latest

View File

@@ -0,0 +1,40 @@
# Use the official .NET 9.0 runtime image as the base image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 5010
# Use the official .NET 9.0 SDK image for building
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# Copy the project file and restore dependencies
COPY ["AIAss.csproj", "./"]
RUN dotnet restore "AIAss.csproj"
# Copy the rest of the source code
COPY . .
WORKDIR "/src/."
# Build the application
RUN dotnet build "AIAss.csproj" -c Release -o /app/build
# Publish the application
FROM build AS publish
RUN dotnet publish "AIAss.csproj" -c Release -o /app/publish /p:UseAppHost=false
# Final stage/image
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
# Set environment variables
ENV ASPNETCORE_URLS=http://+:5010;
ENV ASPNETCORE_ENVIRONMENT=Production
# Create a non-root user for security
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app
USER appuser
# Set the entry point
ENTRYPOINT ["dotnet", "AIAss.dll"]

View File

@@ -0,0 +1,110 @@
# AIAss Docker Setup
This document explains how to run the AIAss gRPC service using Docker.
## Prerequisites
- Docker Desktop installed and running
- Docker Compose (usually comes with Docker Desktop)
## Quick Start
### Using Docker Compose (Recommended)
1. Navigate to the AIAss project directory:
```bash
cd Presentation/AIAss
```
2. Build and run the service:
```bash
docker-compose up --build
```
3. The service will be available at:
- HTTP: http://localhost:5000
- HTTPS: https://localhost:5001
### Using Docker directly
1. Build the Docker image:
```bash
docker build -t aiass .
```
2. Run the container:
```bash
docker run -p 5000:80 -p 5001:443 aiass
```
## Configuration
### Environment Variables
- `ASPNETCORE_ENVIRONMENT`: Set to `Development` or `Production`
- `ASPNETCORE_URLS`: Configure the URLs the service listens on
### Ports
- Port 80: HTTP endpoint
- Port 443: HTTPS endpoint
## Development
### Hot Reload
For development with hot reload, you can mount the source code:
```yaml
volumes:
- .:/src
- /src/bin
- /src/obj
```
### Debugging
To debug the containerized application, you can:
1. Attach a debugger to the running container
2. Use remote debugging tools
3. Check logs: `docker-compose logs aiass`
## Production
For production deployment:
1. Set `ASPNETCORE_ENVIRONMENT=Production`
2. Use proper SSL certificates
3. Configure health checks
4. Set up monitoring and logging
## Troubleshooting
### Common Issues
1. **Port already in use**: Change the port mappings in docker-compose.yml
2. **Build failures**: Ensure all dependencies are properly restored
3. **Permission issues**: Check file ownership and Docker user settings
### Logs
View container logs:
```bash
docker-compose logs -f aiass
```
### Cleanup
Remove containers and images:
```bash
docker-compose down
docker system prune -a
```
## Security Notes
- The container runs as a non-root user (`appuser`)
- Only necessary ports are exposed
- Environment variables can be overridden for different deployment scenarios

View File

@@ -4,19 +4,29 @@ using AIAss.Protos;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text;
using Microsoft.Extensions.Configuration;
namespace AIAss.Services;
public class aiAssistanceService: aiAssistance.aiAssistanceBase
public class aiAssistanceService : aiAssistance.aiAssistanceBase
{
private readonly string Apitoken;
private readonly string url;
public aiAssistanceService(IConfiguration configuration)
{
url = configuration["aiSettings:url"];
Apitoken = configuration["aiSettings:apitoken"];
}
public async override Task<aiaReply> SendQuestion(aiaRequest request, ServerCallContext context)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", request.Apitoken);
new AuthenticationHeaderValue("Bearer", Apitoken);
var content = new
{
model= request.Model,
model = request.Model,
messages = new[]
{
new { role = "system", content = "شما یک دستیار پاسخگو به سوالات هستید." },
@@ -24,7 +34,7 @@ namespace AIAss.Services;
new { role = "system", content = "به سوالات غیره متن بالا پاسخ نده و بگو در این زمینه اطلاعی ندارم" }
}
};
var response = await client.PostAsync(request.Url,
var response = await client.PostAsync(url,
new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json"));
string reponse = "";
if (response.IsSuccessStatusCode)
@@ -35,7 +45,66 @@ namespace AIAss.Services;
}
else
{
reponse="خطا در ارتباط سرور ai";
reponse = "خطا در ارتباط سرور ai";
}
return new aiaReply
{
Message = $" {reponse}"
};
}
public async override Task<aiaReply> imageAnalize(aiaRequest request, ServerCallContext context)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Apitoken);
var content = new
{
model = request.Model,
messages = new object[]
{
new {
role = "system",
content = "شما دستیار برای توصیف آزمایشات هستید."
},
new {
role = "user",
content = new object[]
{
new { type = "text", text = "این تصویر اگر آزمایش انسان است توصیف کن در غیر اینصورت پاسخ نده" },
new {
type = "image_url",
image_url = new {
url = $"data:image/jpeg;base64,{request.Question}"
}
}
}
}
}
};
var response = await client.PostAsync(url,
new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json"));
string reponse = "";
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
reponse = doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString();
context.Status = new Status(StatusCode.OK,"is Success");
}
else
{
reponse = "خطا در ارتباط سرور ai";
if (response.StatusCode==System.Net.HttpStatusCode.TooManyRequests)
{
throw new RpcException(new Status(StatusCode.Cancelled, "ترافیک بالای شبکه در زمان دیگری تلاش کنید"));
}
else
throw new RpcException(new Status(StatusCode.Cancelled, "خطا در ارتباط سرور"));
}
return new aiaReply

View File

@@ -10,5 +10,11 @@
"EndpointDefaults": {
"Protocols": "Http2"
}
},
"aiSettings": {
"url": "https://api.openai.com/v1/chat/completions",
"apitokenold": "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",
"apitoken": "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"
}
}

View File

@@ -24,16 +24,16 @@ namespace AIAss.Protos {
static AiaReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"CglhaWEucHJvdG8SA2FpYSJdCgphaWFSZXF1ZXN0EhAKCHF1ZXN0aW9uGAEg",
"ASgJEg8KB3Byb21wdHMYAiABKAkSDQoFbW9kZWwYAyABKAkSEAoIYXBpdG9r",
"ZW4YBCABKAkSCwoDdXJsGAUgASgJIhsKCGFpYVJlcGx5Eg8KB21lc3NhZ2UY",
"ASABKAkyPgoMYWlBc3Npc3RhbmNlEi4KDFNlbmRRdWVzdGlvbhIPLmFpYS5h",
"aWFSZXF1ZXN0Gg0uYWlhLmFpYVJlcGx5Qg+qAgxBSUFzcy5Qcm90b3NiBnBy",
"b3RvMw=="));
"CglhaWEucHJvdG8SA2FpYSI+CgphaWFSZXF1ZXN0EhAKCHF1ZXN0aW9uGAEg",
"ASgJEg8KB3Byb21wdHMYAiABKAkSDQoFbW9kZWwYAyABKAkiGwoIYWlhUmVw",
"bHkSDwoHbWVzc2FnZRgBIAEoCTJuCgxhaUFzc2lzdGFuY2USLgoMU2VuZFF1",
"ZXN0aW9uEg8uYWlhLmFpYVJlcXVlc3QaDS5haWEuYWlhUmVwbHkSLgoMaW1h",
"Z2VBbmFsaXplEg8uYWlhLmFpYVJlcXVlc3QaDS5haWEuYWlhUmVwbHlCD6oC",
"DEFJQXNzLlByb3Rvc2IGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::AIAss.Protos.aiaRequest), global::AIAss.Protos.aiaRequest.Parser, new[]{ "Question", "Prompts", "Model", "Apitoken", "Url" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::AIAss.Protos.aiaRequest), global::AIAss.Protos.aiaRequest.Parser, new[]{ "Question", "Prompts", "Model" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::AIAss.Protos.aiaReply), global::AIAss.Protos.aiaReply.Parser, new[]{ "Message" }, null, null, null, null)
}));
}
@@ -79,8 +79,6 @@ namespace AIAss.Protos {
question_ = other.question_;
prompts_ = other.prompts_;
model_ = other.model_;
apitoken_ = other.apitoken_;
url_ = other.url_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
@@ -126,30 +124,6 @@ namespace AIAss.Protos {
}
}
/// <summary>Field number for the "apitoken" field.</summary>
public const int ApitokenFieldNumber = 4;
private string apitoken_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Apitoken {
get { return apitoken_; }
set {
apitoken_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "url" field.</summary>
public const int UrlFieldNumber = 5;
private string url_ = "";
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Url {
get { return url_; }
set {
url_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
@@ -168,8 +142,6 @@ namespace AIAss.Protos {
if (Question != other.Question) return false;
if (Prompts != other.Prompts) return false;
if (Model != other.Model) return false;
if (Apitoken != other.Apitoken) return false;
if (Url != other.Url) return false;
return Equals(_unknownFields, other._unknownFields);
}
@@ -180,8 +152,6 @@ namespace AIAss.Protos {
if (Question.Length != 0) hash ^= Question.GetHashCode();
if (Prompts.Length != 0) hash ^= Prompts.GetHashCode();
if (Model.Length != 0) hash ^= Model.GetHashCode();
if (Apitoken.Length != 0) hash ^= Apitoken.GetHashCode();
if (Url.Length != 0) hash ^= Url.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
@@ -212,14 +182,6 @@ namespace AIAss.Protos {
output.WriteRawTag(26);
output.WriteString(Model);
}
if (Apitoken.Length != 0) {
output.WriteRawTag(34);
output.WriteString(Apitoken);
}
if (Url.Length != 0) {
output.WriteRawTag(42);
output.WriteString(Url);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
@@ -242,14 +204,6 @@ namespace AIAss.Protos {
output.WriteRawTag(26);
output.WriteString(Model);
}
if (Apitoken.Length != 0) {
output.WriteRawTag(34);
output.WriteString(Apitoken);
}
if (Url.Length != 0) {
output.WriteRawTag(42);
output.WriteString(Url);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
@@ -269,12 +223,6 @@ namespace AIAss.Protos {
if (Model.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Model);
}
if (Apitoken.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Apitoken);
}
if (Url.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Url);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
@@ -296,12 +244,6 @@ namespace AIAss.Protos {
if (other.Model.Length != 0) {
Model = other.Model;
}
if (other.Apitoken.Length != 0) {
Apitoken = other.Apitoken;
}
if (other.Url.Length != 0) {
Url = other.Url;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
@@ -329,14 +271,6 @@ namespace AIAss.Protos {
Model = input.ReadString();
break;
}
case 34: {
Apitoken = input.ReadString();
break;
}
case 42: {
Url = input.ReadString();
break;
}
}
}
#endif
@@ -364,14 +298,6 @@ namespace AIAss.Protos {
Model = input.ReadString();
break;
}
case 34: {
Apitoken = input.ReadString();
break;
}
case 42: {
Url = input.ReadString();
break;
}
}
}
}

View File

@@ -58,6 +58,14 @@ namespace AIAss.Protos {
__Marshaller_aia_aiaRequest,
__Marshaller_aia_aiaReply);
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
static readonly grpc::Method<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply> __Method_imageAnalize = new grpc::Method<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply>(
grpc::MethodType.Unary,
__ServiceName,
"imageAnalize",
__Marshaller_aia_aiaRequest,
__Marshaller_aia_aiaReply);
/// <summary>Service descriptor</summary>
public static global::Google.Protobuf.Reflection.ServiceDescriptor Descriptor
{
@@ -74,6 +82,12 @@ namespace AIAss.Protos {
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual global::System.Threading.Tasks.Task<global::AIAss.Protos.aiaReply> imageAnalize(global::AIAss.Protos.aiaRequest request, grpc::ServerCallContext context)
{
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
}
}
/// <summary>Client for aiAssistance</summary>
@@ -123,6 +137,26 @@ namespace AIAss.Protos {
{
return CallInvoker.AsyncUnaryCall(__Method_SendQuestion, null, options, request);
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual global::AIAss.Protos.aiaReply imageAnalize(global::AIAss.Protos.aiaRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
{
return imageAnalize(request, new grpc::CallOptions(headers, deadline, cancellationToken));
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual global::AIAss.Protos.aiaReply imageAnalize(global::AIAss.Protos.aiaRequest request, grpc::CallOptions options)
{
return CallInvoker.BlockingUnaryCall(__Method_imageAnalize, null, options, request);
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual grpc::AsyncUnaryCall<global::AIAss.Protos.aiaReply> imageAnalizeAsync(global::AIAss.Protos.aiaRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
{
return imageAnalizeAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken));
}
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
public virtual grpc::AsyncUnaryCall<global::AIAss.Protos.aiaReply> imageAnalizeAsync(global::AIAss.Protos.aiaRequest request, grpc::CallOptions options)
{
return CallInvoker.AsyncUnaryCall(__Method_imageAnalize, null, options, request);
}
/// <summary>Creates a new instance of client from given <c>ClientBaseConfiguration</c>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
protected override aiAssistanceClient NewInstance(ClientBaseConfiguration configuration)
@@ -137,7 +171,8 @@ namespace AIAss.Protos {
public static grpc::ServerServiceDefinition BindService(aiAssistanceBase serviceImpl)
{
return grpc::ServerServiceDefinition.CreateBuilder()
.AddMethod(__Method_SendQuestion, serviceImpl.SendQuestion).Build();
.AddMethod(__Method_SendQuestion, serviceImpl.SendQuestion)
.AddMethod(__Method_imageAnalize, serviceImpl.imageAnalize).Build();
}
/// <summary>Register service method with a service binder with or without implementation. Useful when customizing the service binding logic.
@@ -148,6 +183,7 @@ namespace AIAss.Protos {
public static void BindService(grpc::ServiceBinderBase serviceBinder, aiAssistanceBase serviceImpl)
{
serviceBinder.AddMethod(__Method_SendQuestion, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply>(serviceImpl.SendQuestion));
serviceBinder.AddMethod(__Method_imageAnalize, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::AIAss.Protos.aiaRequest, global::AIAss.Protos.aiaReply>(serviceImpl.imageAnalize));
}
}

View File

@@ -0,0 +1,10 @@
E:\_hushian> docker build -f Presentation\Hushian.WebApi\Dockerfile -t hushianapi .
docker run --name hushian_api -d -p 2201:8080 hushianapi
--------------------------------------------------------------------------------------------
in local => docker build -f Presentation\Hushian.WebApi\Dockerfile -t mmrbnjd/hushianapi:latest .
in local => docker push mmrbnjd/hushianapi:latest
in server => docker pull mmrbnjd/hushianapi:latest
in server => docker run -d -p 2201:8080 --restart always mmrbnjd/hushianapi:latest

View File

@@ -0,0 +1,35 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Presentation/Hushian.WebApi/Hushian.WebApi.csproj", "Presentation/Hushian.WebApi/"]
COPY ["Common/Common.csproj", "Common/"]
COPY ["Hushian.Application/Hushian.Application.csproj", "Hushian.Application/"]
COPY ["Hushian.Domain/Hushian.Domain.csproj", "Hushian.Domain/"]
COPY ["Infrastructure/Infrastructure/Hushian.Infrastructure.csproj", "Infrastructure/Infrastructure/"]
COPY ["Infrastructure/Persistence/Hushian.Persistence.csproj", "Infrastructure/Persistence/"]
RUN dotnet restore "./Presentation/Hushian.WebApi/Hushian.WebApi.csproj"
COPY . .
WORKDIR "/src/Presentation/Hushian.WebApi"
RUN dotnet build "./Hushian.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Hushian.WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Hushian.WebApi.dll"]

View File

@@ -4,6 +4,9 @@
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>a648da2d-f055-444d-91e3-3d53de7d1f5a</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<ItemGroup>
@@ -16,6 +19,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,23 +1,31 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
{
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5089",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5089"
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7046;http://localhost:5089",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7046;http://localhost:5089"
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
}
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
}

View File

@@ -1,6 +1,6 @@
{
"ConnectionStrings": {
"MainConnectionString": "Data Source=195.88.208.142;Initial Catalog=Hushian;User ID=sa;Password=M439610m@;TrustServerCertificate=True"
"MainConnectionString": "Data Source=156.255.1.229;Initial Catalog=Hushian;User ID=sa;Password=SAPassw0rd;TrustServerCertificate=True"
},
"EmailSettings": {
"ApiKey": "SendGrid_Key_Here",
@@ -18,7 +18,7 @@
},
"aigrpcserver": {
"target": "localhost:5010"
"target": "156.255.1.229:5042"
},

View File

@@ -1,5 +1,4 @@

@using Common.Dtos.Verification
@using Common.Dtos.Verification
@using Common.Enums
@using HushianWebApp.Service
@inject VerificationService verificationService;
@@ -10,27 +9,9 @@
</div>
@if (type == VerificationCodeType.ForgetPassword)
{
<div style="justify-content: space-between;margin-top:5px;;margin-bottom:15px">
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code1" @oninput="MoveNext"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input1" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code2" @oninput="MoveNext"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input2" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code3" @oninput="MoveNext"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input3" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code4" @oninput="OnLastInput"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input4" />
</div>
<div style="justify-content: space-between;margin-top:5px;margin-bottom:15px">
<input class="cus-input" maxlength='4' @bind-value="code" @bind-value:event="oninput" />
</div>
<div style="justify-content: space-between">
<TextInput @bind-value=Value type="password" name="Value" style="text-align:center;margin-top: 10px;;margin-left: 10px" placeholder="کلمه عبور جدید" required="required" />
@@ -43,27 +24,7 @@
else
{
<div style="justify-content: space-between;margin-top:5px">
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code1" @oninput="MoveNext"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input1" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code2" @oninput="MoveNext"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input2" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code3" @oninput="MoveNext"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input3" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code4" @oninput="OnLastInput"
style="width: 40px; height: 40px; text-align: center; border: 2px solid #ccc; border-radius: 5px; font-size: 20px;"
@ref="input4" />
<input class="cus-input" maxlength='4' @bind-value="code" @bind-value:event="oninput" />
</div>
}
@@ -82,7 +43,18 @@ else
public string sendValue { get; set; }
[Parameter]
public int? ID { get; set; }
public string? code { get; set; }
private string? _code = string.Empty;
public string? code
{
get { return _code; }
set
{
_code = value;
if (value.Length == 4)
onClick().ConfigureAwait(true);
}
}
[Parameter] public string? Title { get; set; }
[Inject] protected ToastService ToastService { get; set; } = default!;
[Parameter] public EventCallback<VerificationCodeType> OnMultipleOfThree { get; set; }
@@ -92,7 +64,7 @@ else
string resendmsg = "ارسال مجدد";
bool Disabledresendmsg = false;
//-----------------
private string code1, code2, code3, code4;
//private string code1, code2, code3, code4;
private ElementReference input1, input2, input3, input4;
}
@@ -144,14 +116,16 @@ else
else
{
if (Value != ReValue)
{ ToastService.Notify(new(ToastType.Warning, $"کلمه عبور جدید و تکرار متفاوت هستند"));
return;}
{
ToastService.Notify(new(ToastType.Warning, $"کلمه عبور جدید و تکرار متفاوت هستند"));
return;
}
}
}
loading = true;
loading = true;
if (await verificationService.ConfirmedCode(new ConfirmedCodeDto()
{ code = code, codeType = type, Id = ID.Value, value = Value }))
{
@@ -161,35 +135,54 @@ else
}
loading = false;
}
private async Task MoveNext(ChangeEventArgs e)
{
if (e.Value?.ToString()?.Length == 1)
{
if (input1.Context == null) return;
// private async Task MoveNext(ChangeEventArgs e)
// {
// if (e.Value?.ToString()?.Length == 1)
// {
// if (input1.Context == null) return;
if (string.IsNullOrEmpty(code1))
await input2.FocusAsync();
else if (string.IsNullOrEmpty(code2))
await input3.FocusAsync();
else if (string.IsNullOrEmpty(code3))
await input4.FocusAsync();
else if (string.IsNullOrEmpty(code4))
{
code = $"{code1}{code2}{code3}{code4}";
}
}
}
private async Task OnLastInput(ChangeEventArgs e)
{
code4 = e.Value?.ToString();
// if (string.IsNullOrEmpty(code1))
// await input2.FocusAsync();
// else if (string.IsNullOrEmpty(code2))
// await input3.FocusAsync();
// else if (string.IsNullOrEmpty(code3))
// await input4.FocusAsync();
// else if (string.IsNullOrEmpty(code4))
// {
// code = $"{code1}{code2}{code3}{code4}";
// }
// }
// }
// private async Task OnLastInput(ChangeEventArgs e)
// {
// ///code4 = e.Value?.ToString();
if (!string.IsNullOrEmpty(code4) && code4.Length == 1)
{
code = $"{code1}{code2}{code3}{code4}";
if(type==VerificationCodeType.PhoneNumberConfirmed)
await onClick();
}
}
// if (!string.IsNullOrEmpty(code) && code.Length == 4)
// {
// // code = $"{code1}{code2}{code3}{code4}";
// if (type == VerificationCodeType.PhoneNumberConfirmed)
// await onClick();
// }
// }
}
<style>
.cus-input {
direction: ltr;
display: block;
margin: 2em auto;
border: none;
padding: 0;
width: 6ch;
background: repeating-linear-gradient(90deg, dimgrey 0, dimgrey 1ch, transparent 0, transparent 1.5ch) 0 100%/ 5.5ch 2px no-repeat;
font: 5ch droid sans mono, consolas, monospace;
letter-spacing: 0.5ch;
}
input:focus {
outline: none;
color: dodgerblue;
}
</style>

View File

@@ -0,0 +1,12 @@
E:\_hushian> docker build -f Presentation\HushianWebApp\Dockerfile -t hushianapp .
docker run --name hushian_app -d -p 5165:5165 sha256:cd08e4a869cb44e17fafdcf551e7eaa3f26c0c3d1420df78f695359bde08f0fb
--------------------------------------------------------------------------------------------
in local => docker build -f Presentation\HushianWebApp\Dockerfile -t mmrbnjd/hushianapp:latest .
in local => docker push mmrbnjd/hushianapp:latest
in server => docker pull mmrbnjd/hushianapp:latest
in server => docker run -d -p 2101:5165 --restart always mmrbnjd/hushianapp:latest

View File

@@ -0,0 +1,74 @@
# مرحله ۱: Build پروژه Blazor WASM با .NET 9
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# نصب وابستگی‌ها برای emscripten (python3 + سایر ابزارها)
RUN apt-get update && apt-get install -y python3 make cmake clang zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
# کپی کردن فایل‌های پروژه
COPY ["Presentation/HushianWebApp/HushianWebApp.csproj", "Presentation/HushianWebApp/"]
COPY ["Common/Common.csproj", "Common/"]
COPY . .
RUN dotnet workload clean
# نصب wasm-tools برای Blazor
RUN dotnet workload install wasm-tools
# انتشار (Publish) خروجی در حالت Release
RUN dotnet publish "Presentation/HushianWebApp/HushianWebApp.csproj" -c Release -o /app \
-p:TreatWarningsAsErrors=false \
-p:RunAOTCompilation=false \
-p:PublishTrimmed=false
# مرحله ۲: سرو کردن با Nginx
FROM nginx:alpine AS final
WORKDIR /usr/share/nginx/html
# حذف محتوای پیشفرض nginx
RUN rm -rf ./*
# کپی خروجی Blazor WASM
COPY --from=build /app/wwwroot ./
# فقط بلوک server داخل default.conf
RUN printf 'server {\n\
listen 5165;\n\
server_name localhost;\n\
\n\
root /usr/share/nginx/html;\n\
index index.html;\n\
\n\
location / {\n\
try_files $uri $uri/ /index.html;\n\
}\n\
\n\
location /_framework/ {\n\
expires 1y;\n\
add_header Cache-Control "public, immutable";\n\
}\n\
\n\
location /_content/ {\n\
expires 1y;\n\
add_header Cache-Control "public, immutable";\n\
}\n\
\n\
location /Before/ {\n\
expires 7d;\n\
add_header Cache-Control "public";\n\
}\n\
\n\
location ~ \\.dll$ { add_header Content-Type application/octet-stream; }\n\
location ~ \\.wasm$ { add_header Content-Type application/wasm; }\n\
location ~ \\.pdb$ { add_header Content-Type application/octet-stream; }\n\
location ~ \\.dat$ { add_header Content-Type application/octet-stream; }\n\
\n\
gzip on;\n\
gzip_types text/plain application/xml text/css application/javascript application/json application/wasm;\n\
gzip_min_length 256;\n\
\n\
error_page 404 /index.html;\n\
}' > /etc/nginx/conf.d/default.conf
EXPOSE 5165
CMD ["nginx", "-g", "daemon off;"]

File diff suppressed because one or more lines are too long

View File

@@ -18,8 +18,6 @@
Href="/"
IconName="IconName.BootstrapFill"
Title="هوشــیان"
BadgeText="v1.3.1"
ImageSrc="/before/assets/images/logofilehushian.png"
DataProvider="SidebarDataProvider"
Width="200"
WidthUnit="Unit.Px" />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -45,38 +45,17 @@
<div class="d-flex justify-content-center">
<div class="verification-inputs" style="display: flex; gap: 8px; direction: ltr;">
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code1" @oninput="MoveNext"
style="width: 45px; height: 45px; text-align: center; border: 2px solid #ccc; border-radius: 8px; font-size: 18px; font-weight: bold;"
@ref="input1" autoFocus />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code2" @oninput="MoveNext"
style="width: 45px; height: 45px; text-align: center; border: 2px solid #ccc; border-radius: 8px; font-size: 18px; font-weight: bold;"
@ref="input2" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code3" @oninput="MoveNext"
style="width: 45px; height: 45px; text-align: center; border: 2px solid #ccc; border-radius: 8px; font-size: 18px; font-weight: bold;"
@ref="input3" />
<input maxlength="1" type="text" inputmode="numeric" pattern="[0-9]*"
@bind="code4" @oninput="OnLastInput"
style="width: 45px; height: 45px; text-align: center; border: 2px solid #ccc; border-radius: 8px; font-size: 18px; font-weight: bold;"
@ref="input4" />
</div>
<input class="cus-input" maxlength='4' @bind-value="Code" @bind-value:event="oninput"/>
</div>
<Button Color="ButtonColor.Link"
Disabled="@isButtonDisabled"
@onclick="async () =>
Disabled="@isButtonDisabled"
@onclick="async () =>
{
await verificationService.ReSendCode(ID);
await verifiTimer();
}"
Style="padding: 6px 16px; font-size: 14px; background-color: #f8f9fa; transition: all 0.3s ease; display: inline-flex; align-items: center; gap: 8px; margin-top: 10px;margin-bottom: -10px;">
Style="padding: 6px 16px; font-size: 14px; background-color: #f8f9fa; transition: all 0.3s ease; display: inline-flex; align-items: center; gap: 8px; margin-top: 10px;margin-bottom: -10px;">
<span>ارسال مجدد</span>
@@ -98,9 +77,16 @@
[Parameter] public EventCallback OnMultipleOfThree { get; set; }
public string Username { get; set; }
public int ID { get; set; } = 0;
public string Code { get; set; } = string.Empty;
private string _code = string.Empty;
public string Code { get { return _code; } set
{
_code=value;
if (value.Length == 4)
ver().ConfigureAwait(true);
} }
//-----------------
private string code1, code2, code3, code4;
// private string code1, code2, code3, code4;
private ElementReference input1, input2, input3, input4;
string validateStyleUser = "";
private bool isButtonDisabled = true;
@@ -161,35 +147,35 @@
await OnMultipleOfThree.InvokeAsync();
visible = false;
}
private async Task MoveNext(ChangeEventArgs e)
{
if (e.Value?.ToString()?.Length == 1)
{
if (input1.Context == null) return;
// private async Task MoveNext(ChangeEventArgs e)
// {
// if (e.Value?.ToString()?.Length == 1)
// {
// if (input1.Context == null) return;
if (string.IsNullOrEmpty(code1))
await input2.FocusAsync();
else if (string.IsNullOrEmpty(code2))
await input3.FocusAsync();
else if (string.IsNullOrEmpty(code3))
await input4.FocusAsync();
else if (string.IsNullOrEmpty(code4))
{
Code = $"{code1}{code2}{code3}{code4}";
}
}
}
private async Task OnLastInput(ChangeEventArgs e)
{
code4 = e.Value?.ToString();
// if (string.IsNullOrEmpty(code1))
// await input2.FocusAsync();
// else if (string.IsNullOrEmpty(code2))
// await input3.FocusAsync();
// else if (string.IsNullOrEmpty(code3))
// await input4.FocusAsync();
// else if (string.IsNullOrEmpty(code4))
// {
// Code = $"{code1}{code2}{code3}{code4}";
// }
// }
// }
// private async Task OnLastInput(ChangeEventArgs e)
// {
// code4 = e.Value?.ToString();
if (!string.IsNullOrEmpty(code4) && code4.Length == 1)
{
Code = $"{code1}{code2}{code3}{code4}";
// if (!string.IsNullOrEmpty(code4) && code4.Length == 1)
// {
// Code = $"{code1}{code2}{code3}{code4}";
await ver();
}
}
// await ver();
// }
// }
}
<script>
function formatPhoneNumber(input) {
@@ -281,4 +267,22 @@
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
transform: scale(1.05);
}
.cus-input {
direction:ltr;
display: block;
margin: 2em auto;
border: none;
padding: 0;
width: 6ch;
background: repeating-linear-gradient(90deg, dimgrey 0, dimgrey 1ch, transparent 0, transparent 1.5ch) 0 100%/ 5.5ch 2px no-repeat;
font: 5ch droid sans mono, consolas, monospace;
letter-spacing: 0.5ch;
}
input:focus {
outline: none;
color: dodgerblue;
}
</style>

View File

@@ -7,8 +7,8 @@ using HushianWebApp.Service;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("http://localhost:5089/api/") });
string BaseAddress = builder.Configuration.GetSection("BaseAddress").Value;
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(BaseAddress) });
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
builder.Services.AddScoped<BaseController>();
builder.Services.AddScoped<AuthService>();

View File

@@ -0,0 +1,21 @@
server {
listen 5089;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg)$ {
expires 6M;
access_log off;
add_header Cache-Control "public";
}
location = /index.html {
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
}

View File

@@ -0,0 +1,12 @@
worker_processes 1;
events { worker_connections 1024; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
}

View File

@@ -0,0 +1,10 @@
{
"BaseAddress": "https://service.hushian.ir/api/",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -34,7 +34,15 @@
<span class="dismiss">🗙</span>
</div>
<script src="_framework/blazor.webassembly.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(reg => console.log('Service Worker registered', reg))
.catch(err => console.error('Service Worker registration failed:', err));
});
}
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<!-- Add chart.js reference if chart components are used in your application. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.0.1/chart.umd.js" integrity="sha512-gQhCDsnnnUfaRzD8k1L5llCCV6O9HN09zClIzzeJ8OJ9MpGmIlCxm+pdCkqTwqJ4JcjbojFr79rl2F1mzcoLMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

View File

@@ -1,6 +1,6 @@
{
"name": "HushianWebApp",
"short_name": "HushianWebApp",
"name": "هوشیان",
"short_name": "هوشیان",
"id": "./",
"start_url": "./",
"display": "standalone",

1
command.txt Normal file
View File

@@ -0,0 +1 @@
docker compose up -d --build

53
docker-compose.yml Normal file
View File

@@ -0,0 +1,53 @@
version: '3.9'
services:
aiass:
build:
context: ./Presentation/AIAss
dockerfile: Dockerfile
image: aiass:latest
container_name: aiass
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:5010
ports:
- "5011:5010"
networks:
- hushainnet
- aiassnet
webapi:
build:
context: .
dockerfile: Presentation/Hushian.WebApi/Dockerfile
image: hushian.webapi:latest
container_name: hushian-webapi
environment:
- ASPNETCORE_ENVIRONMENT=Production
ports:
- "1011:8080"
- "1012:8081"
networks:
- hushainnet
webapp:
build:
context: .
dockerfile: Presentation/HushianWebApp/Dockerfile
image: hushian.webapp:latest
container_name: hushian-webapp
ports:
- "80:5165"
depends_on:
- webapi
networks:
- hushainnet
networks:
hushainnet:
name: hushainnet
aiassnet:
name: aiassnet