5753 lines
178 KiB
Plaintext
5753 lines
178 KiB
Plaintext
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Program.cs
|
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
namespace xAiApi
|
|
{
|
|
public class Program
|
|
{
|
|
public static void Main(string[] args)
|
|
{
|
|
CreateHostBuilder(args).Build().Run();
|
|
}
|
|
|
|
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
|
Host.CreateDefaultBuilder(args)
|
|
.ConfigureWebHostDefaults(webBuilder =>
|
|
{
|
|
webBuilder.UseStartup<Startup>();
|
|
});
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Startup.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using xAiApi.DI;
|
|
using System.Linq;
|
|
using xAiModels.DI;
|
|
using xTagService.DI;
|
|
using System.Net.Http;
|
|
using xAiApi.Database;
|
|
using xHttpService.DI;
|
|
using xPushService.DI;
|
|
using xFileService.DI;
|
|
using xDataService.DI;
|
|
using Newtonsoft.Json;
|
|
using xAiApi.Providers;
|
|
using System.Reflection;
|
|
using xAiApi.Extensions;
|
|
using xStringService.DI;
|
|
using xAiApi.Interfaces;
|
|
using xStorageService.DI;
|
|
using xIdentityService.DI;
|
|
using xDataService.Models;
|
|
using xCommons.Extensions;
|
|
using xPushService.Helpers;
|
|
using xCommons.Configurations;
|
|
using System.Collections.Generic;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.Extensions.Hosting;
|
|
using xAiApi.Interfaces.Extractors;
|
|
using Newtonsoft.Json.Serialization;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using IdentityModel.AspNetCore.OAuth2Introspection;
|
|
|
|
namespace xAiApi
|
|
{
|
|
public class Startup
|
|
{
|
|
//
|
|
#region Props ...
|
|
public IConfiguration Configuration { get; }
|
|
private readonly List<XDatabaseDescriptor> databases;
|
|
private readonly XAiApiDatabaseDescriptor apiDatabaseDescriptor;
|
|
#endregion
|
|
|
|
//
|
|
#region Constructor(s) ...
|
|
public Startup(IConfiguration configuration)
|
|
{
|
|
//
|
|
Configuration = configuration;
|
|
|
|
//
|
|
// Create an Api Descriptor Class Instance ...
|
|
apiDatabaseDescriptor = new XAiApiDatabaseDescriptor();
|
|
databases = new List<XDatabaseDescriptor>
|
|
{
|
|
apiDatabaseDescriptor
|
|
};
|
|
}
|
|
#endregion
|
|
|
|
public void ConfigureServices(IServiceCollection services)
|
|
{
|
|
//
|
|
// Register Validation Provider and all XCommons Module Services ...
|
|
services.AddXCommons();
|
|
|
|
//
|
|
// Register App Configuration ...
|
|
services.AddXAppConfiguration(Configuration);
|
|
var appConfiguration = services.GetRegisteredService<XAppConfiguration>();
|
|
|
|
//
|
|
// Register Allowed Origins ...
|
|
services.AddXCors(appConfiguration.AllowedOrigins);
|
|
|
|
//
|
|
// Register Swagger ...
|
|
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
|
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
|
|
services.AddXSwagger(Configuration, xmlFilePath: xmlPath);
|
|
|
|
//
|
|
// Register HttpService ...
|
|
services.AddXHttpService(Configuration);
|
|
|
|
//
|
|
// Add Api V1 Versioning ...
|
|
services.AddXApiV1Versioning();
|
|
|
|
//
|
|
// OAuthIntrospectin Http Client Handler ...
|
|
// this is for handling SSLErrors ...
|
|
services.AddHttpClient(OAuth2IntrospectionDefaults.BackChannelHttpClientName)
|
|
.ConfigurePrimaryHttpMessageHandler(() =>
|
|
{
|
|
return new HttpClientHandler
|
|
{
|
|
ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
|
|
};
|
|
});
|
|
|
|
//
|
|
// Register Authorizations ...
|
|
services.AddXAuthorization();
|
|
|
|
//
|
|
services.AddControllers()
|
|
.AddNewtonsoftJson(x =>
|
|
{
|
|
x.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
|
x.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
|
});
|
|
|
|
//
|
|
var lifeTime = ServiceLifetime.Scoped;
|
|
var repositoryType = xDataService.Constants.XRepositoryType.EF;
|
|
|
|
//
|
|
// Register XIdentity Service ...
|
|
services.AddXIdentityService(
|
|
lifeTime: lifeTime,
|
|
configuration: Configuration
|
|
);
|
|
|
|
//
|
|
// Storage Service ...
|
|
services.AddXStorageService(Configuration);
|
|
|
|
//
|
|
// Push Service ...
|
|
services.AddXPushService(
|
|
lifeTime: lifeTime,
|
|
config: Configuration
|
|
);
|
|
|
|
//
|
|
// Preparing DataBases ...
|
|
if (databases.HasChild())
|
|
{
|
|
//
|
|
databases
|
|
.ToList()
|
|
.ForEach(database =>
|
|
{
|
|
//
|
|
// Register DataBase ...
|
|
services.AddXDatabase(
|
|
lifeTime: lifeTime,
|
|
descriptor: database,
|
|
configuration: Configuration
|
|
);
|
|
});
|
|
}
|
|
|
|
//
|
|
// Register String Service ...
|
|
services.AddXStringService<XAiApiDbContext>(
|
|
lifeTime: lifeTime,
|
|
repositoryType: repositoryType
|
|
);
|
|
|
|
//
|
|
// Register Tag Service ...
|
|
services.AddXTagService<XAiApiDbContext>(
|
|
lifeTime: lifeTime,
|
|
repositoryType: repositoryType
|
|
);
|
|
|
|
//
|
|
// Register File Service ...
|
|
services.AddXFileService<XAiApiDbContext>(
|
|
lifeTime: lifeTime,
|
|
repositoryType: repositoryType
|
|
);
|
|
|
|
//
|
|
// Register AiApi Data Service ...
|
|
services.AddXAiDataService<XAiApiDbContext>(
|
|
lifeTime: lifeTime,
|
|
repositoryType: repositoryType
|
|
);
|
|
|
|
//
|
|
// Register XAiApi Configuration ...
|
|
services.AddXAiApiConfiguration(Configuration);
|
|
|
|
//
|
|
// Register File Content Extractors ...
|
|
// services.AddSingleton<IXFileContentExtractor, XPdfContentExtractor>();
|
|
// services.AddSingleton<IXFileContentExtractor, XPlainTextContentExtractor>();
|
|
services.AddSingleton<IXFileContentExtractor, XFileContentExtractor>();
|
|
|
|
//
|
|
// Register Default Ai Service ...
|
|
services.AddScoped<IXDefaultAiService, XDefaultAiService>();
|
|
services.AddScoped<IXDefaultEmbeddingService, XDefaultEmbeddingService>();
|
|
services.AddScoped<IXDefaultThinkingAiService, XDefaultThinkingAiService>();
|
|
}
|
|
|
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
|
{
|
|
//
|
|
// Configure the HTTP request pipeline.
|
|
var isDevelopmentEnvironment = false;
|
|
if (env.IsDevelopment())
|
|
{
|
|
isDevelopmentEnvironment = true;
|
|
app.UseDeveloperExceptionPage();
|
|
}
|
|
|
|
//
|
|
// Use Swagger Middleware ...
|
|
app.UseXSwagger();
|
|
|
|
//
|
|
// Force Https Redirection ...
|
|
app.UseHttpsRedirection();
|
|
|
|
//
|
|
// Using Cors ...
|
|
app.UseXCors();
|
|
|
|
//
|
|
// Using Routing ...
|
|
app.UseRouting();
|
|
|
|
//
|
|
// Use xIdentityService ...
|
|
app.UseXIdentityService();
|
|
|
|
//
|
|
// Use Authorization ...
|
|
app.UseAuthorization();
|
|
|
|
//
|
|
app.UseEndpoints(endpoints =>
|
|
{
|
|
endpoints.MapControllers();
|
|
});
|
|
|
|
//
|
|
// Use Storage Service ...
|
|
app.UseXStorageService();
|
|
|
|
//
|
|
// Use PushService ...
|
|
var pushHelper = new XPushServiceHelper();
|
|
app.UseXPushService(pushHelper);
|
|
|
|
//
|
|
// Using DataBase ...
|
|
if (databases.HasChild())
|
|
{
|
|
//
|
|
databases
|
|
.ToList()
|
|
.ForEach(database =>
|
|
{
|
|
//
|
|
app.UseXDatabase(
|
|
descriptor: database,
|
|
isDevelopmentEnvironment: isDevelopmentEnvironment
|
|
);
|
|
});
|
|
}
|
|
|
|
//
|
|
// Using String Service ...
|
|
app.UseXStringService(
|
|
isDevelopmentEnvironment: isDevelopmentEnvironment
|
|
);
|
|
|
|
//
|
|
// Using XTag Service ...
|
|
app.UseXTagService(
|
|
isDevelopmentEnvironment: isDevelopmentEnvironment
|
|
);
|
|
|
|
//
|
|
// Using File Service ...
|
|
app.UseXFileService(
|
|
isDevelopmentEnvironment: isDevelopmentEnvironment
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Configurations\XAiApiConfiguration.cs
|
|
|
|
using xAiModels.Models;
|
|
|
|
namespace xAiApi.Configurations
|
|
{
|
|
public class XAiApiConfiguration
|
|
{
|
|
/// <summary>
|
|
/// Allowed Models to Use ...
|
|
/// </summary>
|
|
public XAiModelDescriptor[] Models { get; set; } = [];
|
|
|
|
/// <summary>
|
|
/// Allowed Prompts to Use ...
|
|
/// </summary>
|
|
public XAiPromptDescriptor[] Prompts { get; set; } = [];
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Constants\ConfigurationNodeNames.cs
|
|
|
|
namespace xAiApi.Constants
|
|
{
|
|
public partial struct ConfigurationNodeNames
|
|
{
|
|
public const string AI_API_NODE = "AiApiConfiguration";
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Constants\XAiApiConstants.cs
|
|
|
|
namespace xAiApi.Constants
|
|
{
|
|
public struct XAiApiConstants
|
|
{
|
|
//
|
|
// Database Descriptor ...
|
|
public const string XAiApiDbIdentifier = "xAiApiDb";
|
|
public const string XAiApiMigrationsAssemblyIdentifier = "xAiApi";
|
|
|
|
//
|
|
public const string XDILogTag = "XAiApi";
|
|
public const string XAiApiIntroductionPromptName = "Introduction";
|
|
|
|
//
|
|
public const string XAiDefaultModelName = "Gemma";
|
|
public const string XAiEmbeddingModelName = "Embed";
|
|
public const string XAiDefaultThinkingModelName = "Qwen";
|
|
public const string XAiEmbeddingPromptName = "Embedding";
|
|
|
|
//
|
|
public const string XOpenAINoKey = "no-key-required";
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Controllers\DefaultAiController.cs
|
|
|
|
using xAiApi.Interfaces;
|
|
using xCommons.Providers;
|
|
using xCommons.Configurations;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using xIdentityService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
namespace xAiApi.Controllers
|
|
{
|
|
[Authorize]
|
|
[Route("[controller]")]
|
|
public class DefaultAiController : XAiServiceControllerBase<IXDefaultAiService>
|
|
{
|
|
public DefaultAiController(
|
|
ILogger<DefaultAiController> logger,
|
|
IXDefaultAiService aiService,
|
|
XAppConfiguration appConfiguration,
|
|
IXIdentityProvider identityProvider,
|
|
XValidationProvider validationProvider
|
|
) : base(
|
|
logger,
|
|
aiService,
|
|
appConfiguration,
|
|
identityProvider,
|
|
validationProvider
|
|
)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Controllers\DefaultEmbeddingController.cs
|
|
|
|
using xAiApi.Interfaces;
|
|
using xCommons.Providers;
|
|
using xCommons.Configurations;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using xIdentityService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
namespace xAiApi.Controllers
|
|
{
|
|
[Authorize]
|
|
[Route("[controller]")]
|
|
public class DefaultEmbeddingController : XAiEmbeddingServiceControllerBase<IXDefaultEmbeddingService>
|
|
{
|
|
public DefaultEmbeddingController(
|
|
ILogger<DefaultEmbeddingController> logger,
|
|
IXDefaultEmbeddingService aiService,
|
|
XAppConfiguration appConfiguration,
|
|
IXIdentityProvider identityProvider,
|
|
XValidationProvider validationProvider
|
|
) : base(
|
|
logger,
|
|
aiService,
|
|
appConfiguration,
|
|
identityProvider,
|
|
validationProvider
|
|
)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Controllers\DefaultThinkingAiController.cs
|
|
|
|
using xAiApi.Interfaces;
|
|
using xCommons.Providers;
|
|
using xCommons.Configurations;
|
|
using xIdentityService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace xAiApi.Controllers
|
|
{
|
|
public class DefaultThinkingAiController : XAiServiceControllerBase<IXDefaultThinkingAiService>
|
|
{
|
|
public DefaultThinkingAiController(
|
|
ILogger<DefaultThinkingAiController> logger,
|
|
IXDefaultThinkingAiService aiService,
|
|
XAppConfiguration appConfiguration,
|
|
IXIdentityProvider identityProvider,
|
|
XValidationProvider validationProvider
|
|
) : base(
|
|
logger,
|
|
aiService,
|
|
appConfiguration,
|
|
identityProvider,
|
|
validationProvider
|
|
)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Controllers\StartupController.cs
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Logging;
|
|
using xCommons.Configurations;
|
|
using xCommons.Controllers;
|
|
using xCommons.Providers;
|
|
|
|
namespace xAiApi.Controllers
|
|
{
|
|
/// <summary>
|
|
/// Runing when application start ...
|
|
/// </summary>
|
|
[Route("")]
|
|
[AllowAnonymous]
|
|
public class StartupController : XBaseController
|
|
{
|
|
|
|
public StartupController(
|
|
ILogger<StartupController> logger,
|
|
XAppConfiguration appConfiguration,
|
|
XValidationProvider validationProvider
|
|
) : base(
|
|
logger,
|
|
appConfiguration,
|
|
validationProvider
|
|
)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Show Configured Welcome Message
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[HttpGet("")]
|
|
[AllowAnonymous]
|
|
public virtual ActionResult<string> Index()
|
|
{
|
|
//
|
|
var controllerName = GetControllerName();
|
|
var message = $"{AppConfiguration.WelcomeMessage}";
|
|
|
|
//
|
|
return Ok(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Controllers\XAiEmbeddingServiceControllerBase.cs
|
|
|
|
using System;
|
|
using System.Threading;
|
|
using xAiApi.Interfaces;
|
|
using xCommons.Providers;
|
|
using System.Threading.Tasks;
|
|
using xCommons.Configurations;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Collections.Generic;
|
|
using xIdentityService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
namespace xAiApi.Controllers
|
|
{
|
|
[Authorize]
|
|
[Route("[controller]")]
|
|
public abstract class XAiEmbeddingServiceControllerBase<TAiService> : XAiServiceControllerBase<TAiService>, IXAiEmbeddingServiceControllerBase
|
|
where TAiService : IXAiEmbeddingServiceBase
|
|
{
|
|
private readonly TAiService aiService;
|
|
|
|
protected XAiEmbeddingServiceControllerBase(
|
|
ILogger logger,
|
|
TAiService aiService,
|
|
XAppConfiguration appConfiguration,
|
|
IXIdentityProvider identityProvider,
|
|
XValidationProvider validationProvider
|
|
) : base(
|
|
logger,
|
|
aiService,
|
|
appConfiguration,
|
|
identityProvider,
|
|
validationProvider
|
|
)
|
|
{
|
|
this.aiService = aiService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request For Embedding Specified String ...
|
|
/// </summary>
|
|
/// <param name="content"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[AllowAnonymous]
|
|
[HttpGet("Embedding")]
|
|
public async Task<ActionResult<float[]>> Embedding(
|
|
[FromQuery] string content,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
ValidationProvider.NotEmpty(content);
|
|
|
|
//
|
|
var result = await aiService.Embedding(
|
|
content: content,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request For Embedding Specified Batch ...
|
|
/// </summary>
|
|
/// <param name="batch"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[AllowAnonymous]
|
|
[HttpGet("Embeddings")]
|
|
public async Task<ActionResult<IReadOnlyList<float[]>>> Embeddings(
|
|
[FromQuery] string[] batch,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
ValidationProvider.NotZeroChilds(batch);
|
|
|
|
//
|
|
var result = await aiService.Embeddings(
|
|
batch: batch,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Controllers\XAiServiceControllerBase.cs
|
|
|
|
using System;
|
|
using xModels.Dtos;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using xAiApi.Interfaces;
|
|
using xCommons.Providers;
|
|
using xCommons.Extensions;
|
|
using xAiModels.Extensions;
|
|
using xAiModels.Models.Dtos;
|
|
using xExceptions.Constants;
|
|
using System.Threading.Tasks;
|
|
using xCommons.Configurations;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Http;
|
|
using System.Collections.Generic;
|
|
using xIdentityService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using xIdentityService.Controllers;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
namespace xAiApi.Controllers
|
|
{
|
|
[Authorize]
|
|
[Route("[controller]")]
|
|
public abstract class XAiServiceControllerBase<TAiService> : XBaseIdentityApiV1Controller, IXAiServiceControllerBase
|
|
where TAiService : IXAiServiceBase
|
|
{
|
|
private readonly TAiService aiService;
|
|
|
|
protected XAiServiceControllerBase(
|
|
ILogger logger,
|
|
TAiService aiService,
|
|
XAppConfiguration appConfiguration,
|
|
IXIdentityProvider identityProvider,
|
|
XValidationProvider validationProvider
|
|
) : base(
|
|
logger,
|
|
appConfiguration,
|
|
identityProvider,
|
|
validationProvider
|
|
)
|
|
{
|
|
this.aiService = aiService;
|
|
}
|
|
|
|
//
|
|
#region Actions ...
|
|
/// <summary>
|
|
/// Ask Question and Get Response ...
|
|
/// </summary>
|
|
/// <param name="question"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[AllowAnonymous]
|
|
[HttpGet("AskText")]
|
|
public virtual async Task<ActionResult<string>> AskText(
|
|
[FromQuery] string question,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
ValidationProvider.NotEmpty(question);
|
|
|
|
//
|
|
var result = await aiService.AskTextAsync(
|
|
prompt: question,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask Question and Get Response Stream ...
|
|
/// </summary>
|
|
/// <param name="question"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[AllowAnonymous]
|
|
[HttpGet("AskTextStream")]
|
|
public virtual async Task AskTextStream(
|
|
[FromQuery] string question,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
ValidationProvider.NotEmpty(question);
|
|
|
|
//
|
|
// Configure SSE Response Headers ...
|
|
Response.Headers["X-Accel-Buffering"] = "no";
|
|
Response.Headers["Connection"] = "keep-alive";
|
|
Response.Headers["Content-Type"] = "text/event-stream";
|
|
Response.Headers["Cache-Control"] = "no-cache, no-store";
|
|
|
|
//
|
|
// Disable response buffering ...
|
|
var responseBodyFeature = HttpContext.Features
|
|
.Get<Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature>();
|
|
responseBodyFeature?.DisableBuffering();
|
|
|
|
//
|
|
// Stream Response ...
|
|
var jsonOptions = new JsonSerializerOptions
|
|
{
|
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
|
};
|
|
|
|
//
|
|
var enumerable = aiService.AskTextAsEnumerable(
|
|
prompt: question,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
await foreach (var chunk in enumerable)
|
|
{
|
|
//
|
|
// Skip empty chunks ...
|
|
if (string.IsNullOrEmpty(chunk))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Build SSE message ...
|
|
// Format: data: <json>\n\n
|
|
// var payload = JsonSerializer.Serialize(
|
|
// new { text = chunk },
|
|
// jsonOptions
|
|
// );
|
|
var payload = chunk;
|
|
|
|
//
|
|
// Write SSE Event ...
|
|
await Response.WriteAsync($"{payload}\n\n", cancellationToken);
|
|
await Response.Body.FlushAsync(cancellationToken);
|
|
}
|
|
|
|
//
|
|
await Response.Body.FlushAsync(cancellationToken);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
await Response.Body.FlushAsync();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Loaders ...
|
|
/// <summary>
|
|
/// Retrieve AI Projects ...
|
|
/// </summary>
|
|
/// <param name="includeDefaults"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects")]
|
|
public async Task<ActionResult<IEnumerable<XAiProjectDto>>> LoadProjects(
|
|
[FromQuery] bool includeDefaults = false,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
var result = await aiService.LoadProjects(
|
|
ownerId: userInfo.UserId,
|
|
connectionId: connectionId,
|
|
includeDefaults: includeDefaults,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve Specified AI Project's Conversations ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects/{projectId}/Conversations")]
|
|
public async Task<ActionResult<IEnumerable<XAiConversationDto>>> LoadConversations(
|
|
[FromRoute] Guid projectId,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (projectId.IsDefaultGuid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
var result = await aiService.LoadConversations(
|
|
projectId: projectId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve Specified AI Conversation's Messages ...
|
|
/// </summary>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Conversations/{conversationId}/Messages")]
|
|
public async Task<ActionResult<IEnumerable<XAiMessageDto>>> LoadMessages(
|
|
[FromRoute] Guid conversationId,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (conversationId.IsDefaultGuid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await aiService.LoadMessages(
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Queries ...
|
|
/// <summary>
|
|
/// Query Projects ...
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// </remarks>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects/Query")]
|
|
public async Task<ActionResult<XQueryResult<XAiProjectDto>>> QueryProjects(
|
|
[FromQuery] XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
var result = await aiService.QueryProjects(
|
|
query: query,
|
|
ownerId: userInfo.UserId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query Conversations ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects/{projectId}/Conversations/Query")]
|
|
public async Task<ActionResult<XQueryResult<XAiConversationDto>>> QueryConversations(
|
|
[FromRoute] Guid projectId,
|
|
[FromQuery] XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
var result = await aiService.QueryConversations(
|
|
query: query,
|
|
projectId: projectId,
|
|
ownerId: userInfo.UserId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query Messages ...
|
|
/// </summary>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Conversations/{conversationId}/Messages/Query")]
|
|
public async Task<ActionResult<XQueryResult<XAiMessageDto>>> QueryMessages(
|
|
[FromRoute] Guid conversationId,
|
|
[FromQuery] XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
var result = await aiService.QueryMessages(
|
|
query: query,
|
|
ownerId: userInfo.UserId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Creators ...
|
|
/// <summary>
|
|
/// Create an AI Project ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("Projects")]
|
|
public async Task<ActionResult<XAiProjectDto>> CreateProject(
|
|
[FromBody] XAiProjectDto item,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (item.IsNullOrDefault())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
// Update Required Params ...
|
|
item.OwnerId = userInfo.UserId;
|
|
if (!item.IsValidForAddOrUpdate())
|
|
{
|
|
XException.InvalidData.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await aiService.CreateProject(
|
|
item: item,
|
|
ownerId: userInfo.UserId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an AI Conversation ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("Projects/{projectId}/Conversations")]
|
|
public async Task<ActionResult<XAiConversationDto>> CreateConversation(
|
|
[FromRoute] Guid projectId,
|
|
[FromBody] XAiConversationDto item,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (projectId.IsDefaultGuid() ||
|
|
item.IsNullOrDefault())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
// Update Required Params ...
|
|
item.OwnerId = userInfo.UserId;
|
|
if (!item.IsValidForAddOrUpdate())
|
|
{
|
|
XException.InvalidData.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await aiService.CreateConversation(
|
|
item: item,
|
|
projectId: projectId,
|
|
ownerId: userInfo.UserId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Data Actions ...
|
|
/// <summary>
|
|
/// Ask Question and Get Response ...
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("Ask")]
|
|
public async Task<ActionResult<string>> Ask(
|
|
[FromBody] XAiResponseRequest request,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (!request.IsValid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
// TODO: Reading Fiels Form Collection and Attach it ...
|
|
var result = await aiService.AskAsync(
|
|
files: null,
|
|
prompt: request.Prompt,
|
|
ownerId: userInfo.UserId,
|
|
connectionId: connectionId,
|
|
projectId: request.ProjectId,
|
|
cancellationToken: cancellationToken,
|
|
conversationId: request.ConversationId
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask Question and Get Response Stream ...
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("AskStream")]
|
|
public async Task AskStream(
|
|
[FromBody] XAiResponseRequest request,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (!request.IsValid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
// Configure SSE Response Headers ...
|
|
Response.Headers["X-Accel-Buffering"] = "no";
|
|
Response.Headers["Connection"] = "keep-alive";
|
|
Response.Headers["Content-Type"] = "text/event-stream";
|
|
Response.Headers["Cache-Control"] = "no-cache, no-store";
|
|
|
|
//
|
|
// Disable response buffering ...
|
|
var responseBodyFeature = HttpContext.Features
|
|
.Get<Microsoft.AspNetCore.Http.Features.IHttpResponseBodyFeature>();
|
|
responseBodyFeature?.DisableBuffering();
|
|
|
|
//
|
|
// TODO: Reading Fiels Form Collection and Attach it ...
|
|
var enumerable = aiService.AskAsEnumerable(
|
|
files: null,
|
|
prompt: request.Prompt,
|
|
ownerId: userInfo.UserId,
|
|
connectionId: connectionId,
|
|
projectId: request.ProjectId,
|
|
cancellationToken: cancellationToken,
|
|
conversationId: request.ConversationId
|
|
);
|
|
|
|
//
|
|
await foreach (var resp in enumerable)
|
|
{
|
|
//
|
|
// Skip Empty Responses ...
|
|
if (resp.IsNullOrDefault())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
var payload = resp.ToJSON(camelCase: true);
|
|
|
|
//
|
|
// Write SSE Event ...
|
|
await Response.WriteAsync($"{payload}", cancellationToken);
|
|
await Response.Body.FlushAsync(cancellationToken);
|
|
}
|
|
|
|
//
|
|
await Response.Body.FlushAsync(cancellationToken);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
await Response.Body.FlushAsync();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask Question with Files and Get Response ...
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("AskWithFiles")]
|
|
[Consumes("multipart/form-data")]
|
|
public async Task<ActionResult<string>> AskWithFiles(
|
|
[FromForm] XAiResponseRequest request,
|
|
[FromForm] IFormFileCollection files,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (!request.IsValid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var userInfo = await GetUserInfo();
|
|
var connectionId = GetConnectionId();
|
|
|
|
//
|
|
var result = await aiService.AskAsync(
|
|
files: files,
|
|
userInfo: userInfo,
|
|
prompt: request.Prompt,
|
|
ownerId: userInfo.UserId,
|
|
connectionId: connectionId,
|
|
projectId: request.ProjectId,
|
|
cancellationToken: cancellationToken,
|
|
conversationId: request.ConversationId
|
|
);
|
|
|
|
//
|
|
return Ok(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var result = GetExceptionActionResult(ex);
|
|
return result;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Database\XAiApiDatabaseDescriptor.cs
|
|
|
|
using xAiApi.Constants;
|
|
using xDataService.Interfaces;
|
|
using xDataService.Models;
|
|
|
|
namespace xAiApi.Database
|
|
{
|
|
/// <summary>
|
|
/// Ai Api Database Descriptor ...
|
|
/// </summary>
|
|
public class XAiApiDatabaseDescriptor : XDatabaseDescriptor<XAiApiDbContext>, IXDatabaseDescriptor<XAiApiDbContext>
|
|
{
|
|
public XAiApiDatabaseDescriptor() : base(
|
|
name: XAiApiConstants.XAiApiDbIdentifier,
|
|
repositories: []
|
|
)
|
|
{
|
|
OptionsBuilder = b => b.MigrationsAssembly(XAiApiConstants.XAiApiMigrationsAssemblyIdentifier);
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Database\XAiApiDbContext.cs
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
using xAiApi.Constants;
|
|
using xAiModels.Providers;
|
|
using xDataService.Configuration;
|
|
using xDataService.Db;
|
|
using xDataService.Providers;
|
|
using xFileService.Providers;
|
|
using xStringService.Providers;
|
|
using xTagService.Providers;
|
|
|
|
namespace xAiApi.Database
|
|
{
|
|
/// <summary>
|
|
/// Db Context of AiApi ...
|
|
/// </summary>
|
|
public class XAiApiDbContext : XDbContext
|
|
{
|
|
//
|
|
#region DbSets ...
|
|
#endregion
|
|
|
|
//
|
|
#region Navigation Property Entities ...
|
|
#endregion
|
|
|
|
//
|
|
#region Constructor ...
|
|
public XAiApiDbContext(
|
|
DbContextOptions options,
|
|
XDataServiceConfiguration config,
|
|
XEntityRegisterarHandlerService dynamicEntityHandlers
|
|
) : base(
|
|
config: config,
|
|
options: options,
|
|
database: XAiApiConstants.XAiApiDbIdentifier,
|
|
dynamicEntityHandlers: dynamicEntityHandlers,
|
|
dynamicEntityRegisterarsNames: [
|
|
nameof(XTagEntityRegisterar),
|
|
nameof(XFileEntityRegisterar),
|
|
nameof(XStringEntityRegisterar),
|
|
nameof(XAiMessageEntityRegisterar),
|
|
nameof(XAiProjectEntityRegisterar),
|
|
nameof(XAiConversationEntityRegisterar),
|
|
]
|
|
)
|
|
{ }
|
|
#endregion
|
|
|
|
//
|
|
#region Overrides ...
|
|
//
|
|
public override void OnXModelCreating(ModelBuilder modelBuilder)
|
|
{ }
|
|
|
|
//
|
|
public override void OnXConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
{ }
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\DI\XDIHelperExtension.cs
|
|
|
|
using System;
|
|
using xAiApi.Constants;
|
|
using xAiApi.Extensions;
|
|
using xCommons.Extensions;
|
|
using xAiApi.Configurations;
|
|
using xExceptions.Constants;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace xAiApi.DI
|
|
{
|
|
public static partial class XDIHelperExtension
|
|
{
|
|
/// <summary>
|
|
/// Extract AI Configuration ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <returns></returns>
|
|
public static XAiApiConfiguration GetXAiApiConfiguration(this IConfiguration source)
|
|
{
|
|
//
|
|
XAiApiConfiguration result = null;
|
|
|
|
//
|
|
var section = source.GetSection(ConfigurationNodeNames.AI_API_NODE);
|
|
if (!section.IsNull())
|
|
{
|
|
result = section.Get<XAiApiConfiguration>();
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register AiApi Configuration ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="configuration"></param>
|
|
public static void AddXAiApiConfiguration(
|
|
this IServiceCollection source,
|
|
IConfiguration configuration
|
|
)
|
|
{
|
|
//
|
|
var apiConfig = configuration.GetXAiApiConfiguration();
|
|
source.AddXAiApiConfiguration(apiConfig);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register AiApi Configuration ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="configuration"></param>
|
|
public static void AddXAiApiConfiguration(
|
|
this IServiceCollection source,
|
|
XAiApiConfiguration configuration
|
|
)
|
|
{
|
|
//
|
|
if (!configuration.IsValid())
|
|
{
|
|
XException.InvalidConfiguration.Throw();
|
|
}
|
|
|
|
//
|
|
source.AddSingleton(configuration);
|
|
}
|
|
|
|
//
|
|
#region Private ...
|
|
/// <summary>
|
|
/// a LogTag for Service ...
|
|
/// </summary>
|
|
private static string XLogTag = XAiApiConstants.XDILogTag;
|
|
|
|
/// <summary>
|
|
/// print a log in Console ...
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
private static void Log(string message)
|
|
{
|
|
Console.WriteLine($"{XLogTag} => {message}");
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Extensions\XModelsExtensions.cs
|
|
|
|
using System.Linq;
|
|
using xAiApi.Constants;
|
|
using xAiModels.Models;
|
|
using xCommons.Extensions;
|
|
using xAiModels.Extensions;
|
|
using xAiApi.Configurations;
|
|
using System.Collections.Generic;
|
|
|
|
namespace xAiApi.Extensions
|
|
{
|
|
public static class XModelsExtensions
|
|
{
|
|
/// <summary>
|
|
/// Validate Configurations ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <returns></returns>
|
|
public static bool IsValid(this XAiApiConfiguration source)
|
|
{
|
|
//
|
|
var result =
|
|
!source.IsNullOrDefault() &&
|
|
source.Models.HasChild() &&
|
|
source.Models.All(m => m.IsValid()) &&
|
|
source.Prompts.All(m => m.IsValid()) &&
|
|
source.Prompts.Any(p => p.Name
|
|
.Equals(
|
|
XAiApiConstants.XAiApiIntroductionPromptName,
|
|
System.StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract Default Model Descriptor ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <returns></returns>
|
|
public static XAiModelDescriptor GetDefaultModel(this XAiApiConfiguration source)
|
|
{
|
|
//
|
|
XAiModelDescriptor result = null;
|
|
|
|
//
|
|
if (source.IsValid())
|
|
{
|
|
result = source.Models[0];
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve Specified Model from Config ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
public static XAiModelDescriptor GetModel(
|
|
this XAiApiConfiguration source,
|
|
string name
|
|
)
|
|
{
|
|
//
|
|
XAiModelDescriptor result = null;
|
|
|
|
//
|
|
if (source.IsValid())
|
|
{
|
|
//
|
|
if (name.IsNullOrEmpty())
|
|
{
|
|
result = source.GetDefaultModel();
|
|
}
|
|
else
|
|
{
|
|
result = source.Models
|
|
.FirstOrDefault(m => m.Name
|
|
.Equals(name, System.StringComparison.InvariantCultureIgnoreCase));
|
|
}
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieve Introduction Prompt ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <returns></returns>
|
|
public static XAiPromptDescriptor GetIntroductionPrompt(this XAiApiConfiguration source)
|
|
{
|
|
//
|
|
XAiPromptDescriptor result = null;
|
|
|
|
//
|
|
if (source.IsValid())
|
|
{
|
|
//
|
|
result = source.Prompts
|
|
.FirstOrDefault(p => p.Name
|
|
.Equals(
|
|
XAiApiConstants.XAiApiIntroductionPromptName,
|
|
System.StringComparison.InvariantCultureIgnoreCase));
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Select Specified Prompts and Parse it's Template
|
|
/// and Generate Message ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="name"></param>
|
|
/// <param name="params"></param>
|
|
/// <returns></returns>
|
|
public static string GetPrompt(
|
|
this XAiApiConfiguration source,
|
|
string name = null,
|
|
IDictionary<string, string> @params = null
|
|
)
|
|
{
|
|
//
|
|
var result = string.Empty;
|
|
|
|
//
|
|
// Normalize ...
|
|
if (name.IsNullOrEmpty())
|
|
{
|
|
name = XAiApiConstants.XAiApiIntroductionPromptName;
|
|
}
|
|
|
|
//
|
|
if (source.IsValid())
|
|
{
|
|
//
|
|
var prompt = source.Prompts
|
|
.FirstOrDefault(p => p.Name
|
|
.Equals(
|
|
name,
|
|
System.StringComparison.InvariantCultureIgnoreCase
|
|
)
|
|
);
|
|
if (!prompt.IsNullOrDefault())
|
|
{
|
|
//
|
|
result = prompt.Template;
|
|
|
|
//
|
|
// Handle Params ...
|
|
if (!@params.IsNullOrDefault() && @params.HasChild())
|
|
{
|
|
//
|
|
// Select Params ...
|
|
var existsParams = prompt.Params
|
|
.Where(p => @params.Any(pr => pr.Key == p.Name))
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
//
|
|
// Replace Selected Params ...
|
|
existsParams
|
|
.ForEach(pk =>
|
|
{
|
|
result = result.Replace(pk.Param, @params[pk.Name]);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Extensions\XProgramExtensions.cs
|
|
|
|
using System.Collections.Generic;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.HttpOverrides;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using xCommons.Authorization;
|
|
using xCommons.Extensions;
|
|
using xIdentityHelper;
|
|
|
|
namespace xAiApi.Extensions
|
|
{
|
|
public static class XProgramExtensions
|
|
{
|
|
/// <summary>
|
|
/// Register Authorization Policies
|
|
/// </summary>
|
|
/// <param name="services"></param>
|
|
/// <param name="policies"></param>
|
|
public static void AddXAuthorization(
|
|
this IServiceCollection services,
|
|
IDictionary<string, AuthorizationPolicy> policies = null
|
|
)
|
|
{
|
|
//
|
|
services.AddAuthorization(options =>
|
|
{
|
|
//
|
|
if (!policies.IsNull())
|
|
{
|
|
//
|
|
// Fill Policies ...
|
|
using (var policiesEnumerator = policies.GetEnumerator())
|
|
{
|
|
while (policiesEnumerator.MoveNext())
|
|
{
|
|
var policyDescriptor = policiesEnumerator.Current;
|
|
options.AddPolicy(policyDescriptor.Key, policyDescriptor.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
var xPolicies = XAuthorizationHelper.GetXAuthorizationPolicies();
|
|
if (!xPolicies.IsNull())
|
|
{
|
|
//
|
|
// Fill Policies ...
|
|
using (var policiesEnumerator = xPolicies.GetEnumerator())
|
|
{
|
|
while (policiesEnumerator.MoveNext())
|
|
{
|
|
var policyDescriptor = policiesEnumerator.Current;
|
|
options.AddPolicy(policyDescriptor.Key, policyDescriptor.Value);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
//
|
|
services.AddSingleton<IAuthorizationHandler, RequiredRolesHandler>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Use Forward Headers Options for resolving behind a proxy issues ...
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <returns></returns>
|
|
public static void UseXForwardOptions(this IApplicationBuilder source)
|
|
{
|
|
//
|
|
var forwardOptions = new ForwardedHeadersOptions
|
|
{
|
|
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
|
|
RequireHeaderSymmetry = false
|
|
};
|
|
|
|
//
|
|
forwardOptions.KnownNetworks.Clear();
|
|
forwardOptions.KnownProxies.Clear();
|
|
|
|
//
|
|
// ref: https://github.com/aspnet/Docs/issues/2384
|
|
source.UseForwardedHeaders(forwardOptions);
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\IXAiEmbeddingServiceBase.cs
|
|
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.AI;
|
|
using OllamaSharp;
|
|
|
|
namespace xAiApi.Interfaces
|
|
{
|
|
/// <summary>
|
|
/// Base Embedding Provider Service ...
|
|
/// </summary>
|
|
public interface IXAiEmbeddingServiceBase : IXAiServiceBase
|
|
{
|
|
/// <summary>
|
|
/// Embedding Specified String ...
|
|
/// </summary>
|
|
/// <param name="content"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<float[]> Embedding(
|
|
string content,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Embedding Specified Batch ...
|
|
/// </summary>
|
|
/// <param name="batch"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<IReadOnlyList<float[]>> Embeddings(
|
|
string[] batch,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Embedding Generator ...
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
IEmbeddingGenerator<string, Embedding<float>> GetEmbeddingGenerator();
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\IXAiEmbeddingServiceControllerBase.cs
|
|
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace xAiApi.Interfaces
|
|
{
|
|
public interface IXAiEmbeddingServiceControllerBase : IXAiServiceControllerBase
|
|
{
|
|
/// <summary>
|
|
/// Request For Embedding Specified String ...
|
|
/// </summary>
|
|
/// <param name="content"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Embedding")]
|
|
Task<ActionResult<float[]>> Embedding(
|
|
[FromQuery] string content,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Request For Embedding Specified Batch ...
|
|
/// </summary>
|
|
/// <param name="batch"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Embeddings")]
|
|
Task<ActionResult<IReadOnlyList<float[]>>> Embeddings(
|
|
[FromQuery] string[] batch,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\IXAiServiceBase.cs
|
|
|
|
using System;
|
|
using xModels.Dtos;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using xAiModels.Models.Dtos;
|
|
using System.Threading.Tasks;
|
|
using xIdentityModels.Models;
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.AspNetCore.Http;
|
|
using System.Collections.Generic;
|
|
|
|
namespace xAiApi.Interfaces
|
|
{
|
|
/// <summary>
|
|
/// Describe a Base Service Actions of any Ai Based Services ...
|
|
/// </summary>
|
|
public interface IXAiServiceBase : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Chat Options ...
|
|
/// </summary>
|
|
ChatOptions Options { get; }
|
|
|
|
/// <summary>
|
|
/// Descriptor of Models which used in Service ...
|
|
/// </summary>
|
|
XAiModelDescriptor Descriptor { get; }
|
|
|
|
//
|
|
#region Actions ...
|
|
/// <summary>
|
|
/// Base Method for Communicate with LLM ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<string> AskTextAsync(
|
|
string prompt,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Base Method for Communicate with LLM ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
IAsyncEnumerable<string> AskTextAsEnumerable(
|
|
string prompt,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Get LLM Client instance for Communicating with LLM ...
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
IChatClient GetClient();
|
|
|
|
/// <summary>
|
|
/// Create Custom HttpClient for Communicating with LLM API ...
|
|
/// </summary>
|
|
/// <param name="url"></param>
|
|
/// <returns></returns>
|
|
HttpClient GetHttpClient(string url = null);
|
|
|
|
/// <summary>
|
|
/// Prepare a Message History List for LLM Communication based on Prompt ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="forcePrompt"></param>
|
|
/// <returns></returns>
|
|
IList<ChatMessage> GetHistory(
|
|
string prompt = null,
|
|
bool forcePrompt = true
|
|
);
|
|
|
|
/// <summary>
|
|
/// Prepare a Message History List for LLM Communication based on Prompt ...
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
/// <param name="forceMessage"></param>
|
|
/// <returns></returns>
|
|
IList<ChatMessage> GetHistory(
|
|
ChatMessage message = null,
|
|
bool forceMessage = true
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Loaders ...
|
|
/// <summary>
|
|
/// Load AI Projects ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="includeDefaults"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<IEnumerable<XAiProjectDto>> LoadProjects(
|
|
string ownerId,
|
|
string connectionId = null,
|
|
bool includeDefaults = false,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Load Conversations of Specified AI Project ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<IEnumerable<XAiConversationDto>> LoadConversations(
|
|
Guid projectId,
|
|
string connectionId = null,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Load Messages of Specified Conversation ...
|
|
/// </summary>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<IEnumerable<XAiMessageDto>> LoadMessages(
|
|
Guid conversationId,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Queries ...
|
|
/// <summary>
|
|
/// Query Projects ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<XQueryResult<XAiProjectDto>> QueryProjects(
|
|
string ownerId,
|
|
XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Query Conversations ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<XQueryResult<XAiConversationDto>> QueryConversations(
|
|
string ownerId,
|
|
Guid projectId,
|
|
XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Query Messages ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<XQueryResult<XAiMessageDto>> QueryMessages(
|
|
string ownerId,
|
|
Guid conversationId,
|
|
XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Creators ...
|
|
/// <summary>
|
|
/// Create an AI Project ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<XAiProjectDto> CreateProject(
|
|
string ownerId,
|
|
XAiProjectDto item,
|
|
string connectionId = null,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Create an AI Conversation ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<XAiConversationDto> CreateConversation(
|
|
string ownerId,
|
|
Guid projectId,
|
|
XAiConversationDto item,
|
|
string connectionId = null,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Data Actions ...
|
|
/// <summary>
|
|
/// Ask a Question as Prompt and Retrieve Response ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="files"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<XAiMessageDto> AskAsync(
|
|
string prompt,
|
|
string ownerId,
|
|
Guid projectId,
|
|
Guid conversationId,
|
|
string connectionId = null,
|
|
IFormFileCollection files = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Ask a Question as Prompt and Retrieve Response as Async Enumerable ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="files"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
IAsyncEnumerable<XAiResonseMessage> AskAsEnumerable(
|
|
string prompt,
|
|
string ownerId,
|
|
Guid projectId,
|
|
Guid conversationId,
|
|
string connectionId = null,
|
|
IFormFileCollection files = null,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\IXAiServiceControllerBase.cs
|
|
|
|
using System;
|
|
using xModels.Dtos;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using xAiModels.Models.Dtos;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using System.Collections.Generic;
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
namespace xAiApi.Interfaces
|
|
{
|
|
public interface IXAiServiceControllerBase
|
|
{
|
|
//
|
|
#region Actions ...
|
|
/// <summary>
|
|
/// Ask Question and Get Response ...
|
|
/// </summary>
|
|
/// <param name="question"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("AskText")]
|
|
Task<ActionResult<string>> AskText(
|
|
[FromQuery] string question,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Ask Question and Get Response Stream ...
|
|
/// </summary>
|
|
/// <param name="question"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("AskTextStream")]
|
|
Task AskTextStream(
|
|
[FromQuery] string question,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Loaders ...
|
|
/// <summary>
|
|
/// Retrieve AI Projects ...
|
|
/// </summary>
|
|
/// <param name="includeDefaults"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects")]
|
|
Task<ActionResult<IEnumerable<XAiProjectDto>>> LoadProjects(
|
|
[FromQuery] bool includeDefaults = false,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Retrieve Specified AI Project's Conversations ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects/{projectId}/Conversations")]
|
|
Task<ActionResult<IEnumerable<XAiConversationDto>>> LoadConversations(
|
|
[FromRoute] Guid projectId,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Retrieve Specified AI Conversation's Messages ...
|
|
/// </summary>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Conversations/{conversationId}/Messages")]
|
|
Task<ActionResult<IEnumerable<XAiMessageDto>>> LoadMessages(
|
|
[FromRoute] Guid conversationId,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Queries ...
|
|
/// <summary>
|
|
/// Query Projects ...
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// </remarks>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects/Query")]
|
|
Task<ActionResult<XQueryResult<XAiProjectDto>>> QueryProjects(
|
|
[FromQuery] XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Query Conversations ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Projects/{projectId}/Conversations/Query")]
|
|
Task<ActionResult<XQueryResult<XAiConversationDto>>> QueryConversations(
|
|
[FromRoute] Guid projectId,
|
|
[FromQuery] XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Query Messages ...
|
|
/// </summary>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpGet("Conversations/{conversationId}/Messages/Query")]
|
|
Task<ActionResult<XQueryResult<XAiMessageDto>>> QueryMessages(
|
|
[FromRoute] Guid conversationId,
|
|
[FromQuery] XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Creators ...
|
|
/// <summary>
|
|
/// Create an AI Project ...
|
|
/// </summary>
|
|
/// <param name="item"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("Projects")]
|
|
Task<ActionResult<XAiProjectDto>> CreateProject(
|
|
[FromBody] XAiProjectDto item,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Create an AI Conversation ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("Projects/{projectId}/Conversations")]
|
|
Task<ActionResult<XAiConversationDto>> CreateConversation(
|
|
[FromRoute] Guid projectId,
|
|
[FromBody] XAiConversationDto item,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
|
|
//
|
|
#region Data Actions ...
|
|
/// <summary>
|
|
/// Ask Question and Get Response ...
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("Ask")]
|
|
Task<ActionResult<string>> Ask(
|
|
[FromBody] XAiResponseRequest request,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Ask Question and Get Response Stream ...
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("AskStream")]
|
|
Task AskStream(
|
|
[FromBody] XAiResponseRequest request,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Ask Question with Files and Get Response ...
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[HttpPost("AskWithFiles")]
|
|
[Consumes("multipart/form-data")]
|
|
Task<ActionResult<string>> AskWithFiles(
|
|
[FromForm] XAiResponseRequest request,
|
|
[FromForm] IFormFileCollection files,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\IXDefaultAiService.cs
|
|
|
|
namespace xAiApi.Interfaces
|
|
{
|
|
public interface IXDefaultAiService : IXAiServiceBase
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\IXDefaultEmbeddingService.cs
|
|
|
|
namespace xAiApi.Interfaces
|
|
{
|
|
public interface IXDefaultEmbeddingService : IXAiEmbeddingServiceBase
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\IXDefaultThinkingAiService.cs
|
|
|
|
namespace xAiApi.Interfaces
|
|
{
|
|
public interface IXDefaultThinkingAiService: IXAiServiceBase
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\Extractors\IXAudioFileContentExtractor.cs
|
|
|
|
namespace xAiApi.Interfaces.Extractors
|
|
{
|
|
public interface IXAudioFileContentExtractor : IXFileContentExtractor
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\Extractors\IXDocxFileContentExtractor.cs
|
|
|
|
namespace xAiApi.Interfaces.Extractors
|
|
{
|
|
public interface IXDocxFileContentExtractor : IXFileContentExtractor
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\Extractors\IXExcelFileContentExtractor.cs
|
|
|
|
namespace xAiApi.Interfaces.Extractors
|
|
{
|
|
public interface IXExcelFileContentExtractor : IXFileContentExtractor
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\Extractors\IXFileContentExtractor.cs
|
|
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using xAiModels.Models;
|
|
|
|
namespace xAiApi.Interfaces.Extractors
|
|
{
|
|
/// <summary>
|
|
/// a Service for Extracting File Content ...
|
|
/// </summary>
|
|
public interface IXFileContentExtractor
|
|
{
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
bool CanExtract(string mimeType);
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
);
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\Extractors\IXImageFileContentExtractor.cs
|
|
|
|
namespace xAiApi.Interfaces.Extractors
|
|
{
|
|
public interface IXImageFileContentExtractor : IXFileContentExtractor
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\Extractors\IXPdfFileContentExtractor.cs
|
|
|
|
namespace xAiApi.Interfaces.Extractors
|
|
{
|
|
public interface IXPdfFileContentExtractor : IXFileContentExtractor
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Interfaces\Extractors\IXPlainTextFileContentExtractor.cs
|
|
|
|
namespace xAiApi.Interfaces.Extractors
|
|
{
|
|
public interface IXPlainTextFileContentExtractor : IXFileContentExtractor
|
|
{ }
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Migrations\20261003044828_InitialMigrationAiApi.cs
|
|
|
|
using System;
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
|
|
namespace xAiApi.Migrations
|
|
{
|
|
public partial class InitialMigrationAiApi : Migration
|
|
{
|
|
protected override void Up(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.CreateTable(
|
|
name: "AiConversations",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<Guid>(nullable: false),
|
|
Deleted = table.Column<bool>(nullable: false),
|
|
OwnerId = table.Column<string>(maxLength: 255, nullable: false),
|
|
Title = table.Column<string>(maxLength: 255, nullable: false),
|
|
CreatedOn = table.Column<DateTime>(nullable: false),
|
|
UpdatedAt = table.Column<DateTime>(nullable: false),
|
|
ProjectId = table.Column<Guid>(nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_AiConversations", x => x.Id);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "AiMessages",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<Guid>(nullable: false),
|
|
Deleted = table.Column<bool>(nullable: false),
|
|
OwnerId = table.Column<string>(maxLength: 255, nullable: false),
|
|
Content = table.Column<string>(nullable: false),
|
|
Role = table.Column<int>(nullable: false),
|
|
CreatedOn = table.Column<DateTime>(nullable: false),
|
|
UpdatedAt = table.Column<DateTime>(nullable: false),
|
|
ConversationId = table.Column<Guid>(maxLength: 255, nullable: false),
|
|
MetaDatas = table.Column<string>(nullable: true)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_AiMessages", x => x.Id);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "AiProjects",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<Guid>(nullable: false),
|
|
Deleted = table.Column<bool>(nullable: false),
|
|
OwnerId = table.Column<string>(maxLength: 255, nullable: false),
|
|
Title = table.Column<string>(maxLength: 255, nullable: false),
|
|
Description = table.Column<string>(maxLength: 1000, nullable: true),
|
|
Prompt = table.Column<string>(nullable: true),
|
|
CreatedOn = table.Column<DateTime>(nullable: false),
|
|
UpdatedAt = table.Column<DateTime>(nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_AiProjects", x => x.Id);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "Files",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<Guid>(nullable: false),
|
|
Deleted = table.Column<bool>(nullable: false),
|
|
Type = table.Column<int>(nullable: false),
|
|
OwnerId = table.Column<string>(maxLength: 255, nullable: false),
|
|
Name = table.Column<string>(maxLength: 255, nullable: false),
|
|
Path = table.Column<string>(nullable: false),
|
|
Thumb = table.Column<string>(nullable: true),
|
|
ThumbPath = table.Column<string>(nullable: true),
|
|
UploadedOn = table.Column<DateTime>(nullable: false),
|
|
FileName = table.Column<string>(nullable: true),
|
|
References = table.Column<string>(nullable: true)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_Files", x => x.Id);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "Strings",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Deleted = table.Column<bool>(nullable: false),
|
|
Language = table.Column<string>(maxLength: 10, nullable: false),
|
|
ResourceTitle = table.Column<string>(maxLength: 255, nullable: false),
|
|
TranslatedValue = table.Column<string>(nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_Strings", x => x.Id);
|
|
});
|
|
|
|
migrationBuilder.CreateTable(
|
|
name: "Tags",
|
|
columns: table => new
|
|
{
|
|
Id = table.Column<int>(nullable: false)
|
|
.Annotation("Sqlite:Autoincrement", true),
|
|
Deleted = table.Column<bool>(nullable: false),
|
|
Tag = table.Column<string>(maxLength: 255, nullable: false),
|
|
References = table.Column<string>(nullable: true)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_Tags", x => x.Id);
|
|
});
|
|
}
|
|
|
|
protected override void Down(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.DropTable(
|
|
name: "AiConversations");
|
|
|
|
migrationBuilder.DropTable(
|
|
name: "AiMessages");
|
|
|
|
migrationBuilder.DropTable(
|
|
name: "AiProjects");
|
|
|
|
migrationBuilder.DropTable(
|
|
name: "Files");
|
|
|
|
migrationBuilder.DropTable(
|
|
name: "Strings");
|
|
|
|
migrationBuilder.DropTable(
|
|
name: "Tags");
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Migrations\20261003044828_InitialMigrationAiApi.Designer.cs
|
|
|
|
// <auto-generated />
|
|
using System;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Migrations;
|
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
using xAiApi.Database;
|
|
|
|
namespace xAiApi.Migrations
|
|
{
|
|
[DbContext(typeof(XAiApiDbContext))]
|
|
[Migration("20261003044828_InitialMigrationAiApi")]
|
|
partial class InitialMigrationAiApi
|
|
{
|
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
{
|
|
#pragma warning disable 612, 618
|
|
modelBuilder
|
|
.HasAnnotation("ProductVersion", "3.1.14");
|
|
|
|
modelBuilder.Entity("xAiModels.Models.Entities.XAiConversation", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<DateTime>("CreatedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<Guid>("ProjectId")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Title")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<DateTime>("UpdatedAt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("AiConversations");
|
|
});
|
|
|
|
modelBuilder.Entity("xAiModels.Models.Entities.XAiMessage", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Content")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<Guid>("ConversationId")
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<DateTime>("CreatedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("MetaDatas")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<int>("Role")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<DateTime>("UpdatedAt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("AiMessages");
|
|
});
|
|
|
|
modelBuilder.Entity("xAiModels.Models.Entities.XAiProject", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<DateTime>("CreatedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("Description")
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(1000);
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("Prompt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Title")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<DateTime>("UpdatedAt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("AiProjects");
|
|
});
|
|
|
|
modelBuilder.Entity("xFileService.Models.Entities.XFile", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("FileName")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Name")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("Path")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("References")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Thumb")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("ThumbPath")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<int>("Type")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<DateTime>("UploadedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("Files");
|
|
});
|
|
|
|
modelBuilder.Entity("xStringService.Models.Entities.XString", b =>
|
|
{
|
|
b.Property<int>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("Language")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(10);
|
|
|
|
b.Property<string>("ResourceTitle")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("TranslatedValue")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("Strings");
|
|
});
|
|
|
|
modelBuilder.Entity("xTagService.Models.Entities.XTag", b =>
|
|
{
|
|
b.Property<int>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("References")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Tag")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("Tags");
|
|
});
|
|
#pragma warning restore 612, 618
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Migrations\XAiApiDbContextModelSnapshot.cs
|
|
|
|
// <auto-generated />
|
|
using System;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
using xAiApi.Database;
|
|
|
|
namespace xAiApi.Migrations
|
|
{
|
|
[DbContext(typeof(XAiApiDbContext))]
|
|
partial class XAiApiDbContextModelSnapshot : ModelSnapshot
|
|
{
|
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
|
{
|
|
#pragma warning disable 612, 618
|
|
modelBuilder
|
|
.HasAnnotation("ProductVersion", "3.1.14");
|
|
|
|
modelBuilder.Entity("xAiModels.Models.Entities.XAiConversation", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<DateTime>("CreatedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<Guid>("ProjectId")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Title")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<DateTime>("UpdatedAt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("AiConversations");
|
|
});
|
|
|
|
modelBuilder.Entity("xAiModels.Models.Entities.XAiMessage", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Content")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<Guid>("ConversationId")
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<DateTime>("CreatedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("MetaDatas")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<int>("Role")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<DateTime>("UpdatedAt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("AiMessages");
|
|
});
|
|
|
|
modelBuilder.Entity("xAiModels.Models.Entities.XAiProject", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<DateTime>("CreatedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("Description")
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(1000);
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("Prompt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Title")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<DateTime>("UpdatedAt")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("AiProjects");
|
|
});
|
|
|
|
modelBuilder.Entity("xFileService.Models.Entities.XFile", b =>
|
|
{
|
|
b.Property<Guid>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("FileName")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Name")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("OwnerId")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("Path")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("References")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Thumb")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("ThumbPath")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<int>("Type")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<DateTime>("UploadedOn")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("Files");
|
|
});
|
|
|
|
modelBuilder.Entity("xStringService.Models.Entities.XString", b =>
|
|
{
|
|
b.Property<int>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("Language")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(10);
|
|
|
|
b.Property<string>("ResourceTitle")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.Property<string>("TranslatedValue")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT");
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("Strings");
|
|
});
|
|
|
|
modelBuilder.Entity("xTagService.Models.Entities.XTag", b =>
|
|
{
|
|
b.Property<int>("Id")
|
|
.ValueGeneratedOnAdd()
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<bool>("Deleted")
|
|
.HasColumnType("INTEGER");
|
|
|
|
b.Property<string>("References")
|
|
.HasColumnType("TEXT");
|
|
|
|
b.Property<string>("Tag")
|
|
.IsRequired()
|
|
.HasColumnType("TEXT")
|
|
.HasMaxLength(255);
|
|
|
|
b.HasKey("Id");
|
|
|
|
b.ToTable("Tags");
|
|
});
|
|
#pragma warning restore 612, 618
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\XAiEmbeddingServiceBase.cs
|
|
|
|
using System;
|
|
using OpenAI;
|
|
using System.Linq;
|
|
using OllamaSharp;
|
|
using System.Threading;
|
|
using xAiApi.Constants;
|
|
using xAiModels.Models;
|
|
using xAiApi.Extensions;
|
|
using xAiApi.Interfaces;
|
|
using xAiService.Helpers;
|
|
using System.ClientModel;
|
|
using xCommons.Extensions;
|
|
using xAiModels.Constants;
|
|
using xAiModels.Extensions;
|
|
using xAiModels.Interfaces;
|
|
using xAiApi.Configurations;
|
|
using xExceptions.Constants;
|
|
using System.Threading.Tasks;
|
|
using xFileService.Interfaces;
|
|
using Microsoft.Extensions.AI;
|
|
using System.Collections.Generic;
|
|
using Microsoft.Extensions.Logging;
|
|
using xAiApi.Interfaces.Extractors;
|
|
using System.ClientModel.Primitives;
|
|
using xCommons.Providers;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
/// <summary>
|
|
/// Base Embedding Provider Service ...
|
|
/// </summary>
|
|
public abstract class XAiEmbeddingServiceBase : XAIServiceBase, IXAiEmbeddingServiceBase
|
|
{
|
|
private readonly IXFileProvider fileProvider;
|
|
private readonly XAiModelDescriptor descriptor;
|
|
private readonly ILogger<XAiEmbeddingServiceBase> logger;
|
|
|
|
protected XAiEmbeddingServiceBase(
|
|
IXFileProvider fileProvider,
|
|
IXAiDataProvider dataProvider,
|
|
XAiApiConfiguration configuration,
|
|
XValidationProvider validationProvider,
|
|
ILogger<XAiEmbeddingServiceBase> logger,
|
|
IXFileContentExtractor fileContentExtractor,
|
|
string embeddingModelName = XAiApiConstants.XAiEmbeddingModelName
|
|
) : base(
|
|
fileProvider,
|
|
dataProvider,
|
|
logger,
|
|
configuration,
|
|
validationProvider,
|
|
fileContentExtractor,
|
|
null
|
|
)
|
|
{
|
|
//
|
|
// Prepare Embedding Model Descriptor ...
|
|
descriptor = configuration.GetModel(embeddingModelName);
|
|
if (!descriptor.IsValid())
|
|
{
|
|
XException.InvalidConfiguration.Throw();
|
|
}
|
|
|
|
this.fileProvider = fileProvider;
|
|
|
|
//
|
|
this.logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Embedding Specified String ...
|
|
/// </summary>
|
|
/// <param name="content"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public virtual async Task<float[]> Embedding(
|
|
string content,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (content.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
using var generator = GetEmbeddingGenerator();
|
|
|
|
//
|
|
GeneratedEmbeddings<Embedding<float>> response = null;
|
|
response = await generator.GenerateAsync(
|
|
values: [content],
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
if (response.IsNullOrDefault())
|
|
{
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
// Generae Result ...
|
|
var result = response
|
|
.First()?
|
|
.Vector
|
|
.ToArray()
|
|
?? [];
|
|
if (result.Length == 0)
|
|
{
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
// Normalize ...
|
|
VectorHelper.Normalize(result);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Embedding Specified Batch ...
|
|
/// </summary>
|
|
/// <param name="batch"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public virtual async Task<IReadOnlyList<float[]>> Embeddings(
|
|
string[] batch,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (!batch.HasChild() ||
|
|
!batch.All(x => !x.IsNullOrEmpty())
|
|
)
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
using var generator = GetEmbeddingGenerator();
|
|
|
|
//
|
|
GeneratedEmbeddings<Embedding<float>> response = null;
|
|
response = await generator.GenerateAsync(
|
|
values: [.. batch],
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
if (response.IsNullOrDefault())
|
|
{
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
var result = response
|
|
.Select(e => e.Vector.ToArray())
|
|
.ToList();
|
|
|
|
//
|
|
// Normalize ...
|
|
foreach (var vector in result)
|
|
{
|
|
VectorHelper.Normalize(vector);
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Embedding Generator ...
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public IEmbeddingGenerator<string, Embedding<float>> GetEmbeddingGenerator()
|
|
{
|
|
//
|
|
IEmbeddingGenerator<string, Embedding<float>> result = null;
|
|
|
|
//
|
|
if (!descriptor.IsValid())
|
|
{
|
|
XException.InvalidData.Throw("Invalid Embedding Model ...");
|
|
}
|
|
|
|
//
|
|
switch (descriptor.Provider)
|
|
{
|
|
//
|
|
case XAiModelProviderType.OpenAI:
|
|
//
|
|
var openAiClient = new OpenAIClient(
|
|
new ApiKeyCredential(descriptor.ApiKey.IsNullOrEmpty() ? XAiApiConstants.XOpenAINoKey : descriptor.ApiKey),
|
|
new OpenAIClientOptions
|
|
{
|
|
Endpoint = new Uri(descriptor.Url),
|
|
Transport = new HttpClientPipelineTransport(GetHttpClient(descriptor.Url))
|
|
}
|
|
);
|
|
result = openAiClient
|
|
.GetEmbeddingClient(descriptor.LLM)
|
|
.AsIEmbeddingGenerator();
|
|
break;
|
|
|
|
//
|
|
case XAiModelProviderType.Ollama:
|
|
//
|
|
var ollamaClient = new OllamaApiClient(new Uri(descriptor.Url));
|
|
result = ollamaClient;
|
|
break;
|
|
}
|
|
|
|
//
|
|
if (result.IsNull())
|
|
{
|
|
XException.InvalidData.Throw("Invalid Embedding Model ...");
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\XAIServiceBase.cs
|
|
|
|
using System;
|
|
using OpenAI;
|
|
using OllamaSharp;
|
|
using System.Linq;
|
|
using xModels.Dtos;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using xAiApi.Constants;
|
|
using xAiModels.Models;
|
|
using xAiApi.Interfaces;
|
|
using xAiApi.Extensions;
|
|
using System.ClientModel;
|
|
using xCommons.Providers;
|
|
using xCommons.Extensions;
|
|
using xAiModels.Constants;
|
|
using xAiModels.Interfaces;
|
|
using xAiModels.Extensions;
|
|
using xAiService.Constants;
|
|
using xAiApi.Configurations;
|
|
using xExceptions.Constants;
|
|
using xAiModels.Models.Dtos;
|
|
using xAiService.Extensions;
|
|
using System.Threading.Tasks;
|
|
using xIdentityModels.Models;
|
|
using xFileService.Interfaces;
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.AspNetCore.Http;
|
|
using System.Collections.Generic;
|
|
using Microsoft.Extensions.Logging;
|
|
using xAiApi.Interfaces.Extractors;
|
|
using System.ClientModel.Primitives;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
/// <summary>
|
|
/// Base Ai Service ...
|
|
/// </summary>
|
|
public abstract class XAIServiceBase : IXAiServiceBase
|
|
{
|
|
/// <summary>
|
|
/// Chat Options ...
|
|
/// </summary>
|
|
public ChatOptions Options { get; }
|
|
|
|
/// <summary>
|
|
/// Descriptor of Models which used in Service ...
|
|
/// </summary>
|
|
public XAiModelDescriptor Descriptor { get; }
|
|
|
|
private readonly IXFileProvider fileProvider;
|
|
private readonly IXAiDataProvider dataProvider;
|
|
private readonly ILogger<XAIServiceBase> logger;
|
|
private readonly XAiApiConfiguration configuration;
|
|
private readonly XValidationProvider validationProvider;
|
|
private readonly IXFileContentExtractor fileContentExtractor;
|
|
|
|
protected XAIServiceBase(
|
|
IXFileProvider fileProvider,
|
|
IXAiDataProvider dataProvider,
|
|
ILogger<XAIServiceBase> logger,
|
|
XAiApiConfiguration configuration,
|
|
XValidationProvider validationProvider,
|
|
IXFileContentExtractor fileContentExtractor,
|
|
string model = null
|
|
)
|
|
{
|
|
//
|
|
this.logger = logger;
|
|
this.fileProvider = fileProvider;
|
|
this.dataProvider = dataProvider;
|
|
this.configuration = configuration;
|
|
this.validationProvider = validationProvider;
|
|
this.fileContentExtractor = fileContentExtractor;
|
|
|
|
//
|
|
// Prepare Embedding Model Descriptor ...
|
|
Descriptor = configuration.GetModel(model);
|
|
if (!Descriptor.IsValid())
|
|
{
|
|
XException.InvalidConfiguration.Throw();
|
|
}
|
|
|
|
//
|
|
// Prepare Chat Options based On Tools ...
|
|
Options = new ChatOptions();
|
|
}
|
|
|
|
//
|
|
#region Actions ...
|
|
/// <summary>
|
|
/// Base Method for Communicate with LLM ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public virtual async Task<string> AskTextAsync(
|
|
string prompt,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Check Prompt Exists ...
|
|
if (prompt.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
using var client = GetClient();
|
|
var history = GetHistory(prompt);
|
|
var response = await client.GetResponseAsync(
|
|
messages: history,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Validate Response ...
|
|
if (!response.IsValid())
|
|
{
|
|
//
|
|
// Dispose Client ...
|
|
client.Dispose();
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
// Retrieve Response Text ...
|
|
var result = response.Text;
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base Method for Communicate with LLM ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public virtual async IAsyncEnumerable<string> AskTextAsEnumerable(
|
|
string prompt,
|
|
[EnumeratorCancellation]
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Check Prompt Exists ...
|
|
if (prompt.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
using var client = GetClient();
|
|
var history = GetHistory(prompt);
|
|
var enumerable = client.GetStreamingResponseAsync(
|
|
options: null,
|
|
messages: history
|
|
);
|
|
|
|
//
|
|
await foreach (var res in enumerable)
|
|
{
|
|
//
|
|
// Cancellation Token ...
|
|
if (cancellationToken.IsCancellationRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
//
|
|
yield return res.Text;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get LLM Client instance for Communicating with LLM ...
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public virtual IChatClient GetClient()
|
|
{
|
|
//
|
|
// Try to Initialize LLm ...
|
|
var model = Descriptor.LLM;
|
|
var apiKey = Descriptor.ApiKey;
|
|
var url = new Uri(Descriptor.Url);
|
|
var httpClient = GetHttpClient(Descriptor.Url);
|
|
|
|
//
|
|
IChatClient result = null;
|
|
switch (Descriptor.Provider)
|
|
{
|
|
//
|
|
case XAiModelProviderType.Ollama:
|
|
//
|
|
// Instance Ollama Client using Custom HttpCLient ...
|
|
var ollamaClient = new OllamaApiClient(httpClient, model);
|
|
result =
|
|
new ChatClientBuilder(ollamaClient)
|
|
.UseFunctionInvocation()
|
|
.Build();
|
|
break;
|
|
|
|
//
|
|
case XAiModelProviderType.OpenAI:
|
|
//
|
|
// Instance Ollama Client using Custom HttpCLient ...
|
|
var openAiClient = new OpenAIClient(
|
|
new ApiKeyCredential(apiKey.IsNullOrEmpty() ? XAiApiConstants.XOpenAINoKey : apiKey),
|
|
new OpenAIClientOptions
|
|
{
|
|
Endpoint = url,
|
|
Transport = new HttpClientPipelineTransport(httpClient)
|
|
}
|
|
);
|
|
result =
|
|
new ChatClientBuilder(
|
|
openAiClient
|
|
.GetChatClient(model)
|
|
.AsIChatClient())
|
|
.UseFunctionInvocation()
|
|
.Build();
|
|
break;
|
|
|
|
//
|
|
case XAiModelProviderType.DeepSeek:
|
|
break;
|
|
|
|
//
|
|
case XAiModelProviderType.HuggingFace:
|
|
break;
|
|
|
|
//
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//
|
|
if (result.IsNullOrDefault())
|
|
{
|
|
XException.InvalidData.Throw();
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create Custom HttpClient for Communicating with LLM API ...
|
|
/// </summary>
|
|
/// <param name="url"></param>
|
|
/// <returns></returns>
|
|
public virtual HttpClient GetHttpClient(string url = null)
|
|
{
|
|
//
|
|
var result = new HttpClient
|
|
{
|
|
//
|
|
// Disable timeout completely (not recommended for production)
|
|
Timeout = Timeout.InfiniteTimeSpan,
|
|
|
|
//
|
|
BaseAddress = url.IsNullOrEmpty()
|
|
? null
|
|
: new Uri(url),
|
|
|
|
//
|
|
// OR set a very long timeout (e.g., 30 minutes for large file uploads)
|
|
// Timeout = TimeSpan.FromMinutes(30)
|
|
};
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare a Message History List for LLM Communication based on Prompt ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="forcePrompt"></param>
|
|
/// <returns></returns>
|
|
public virtual IList<ChatMessage> GetHistory(
|
|
string prompt = null,
|
|
bool forcePrompt = true
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
if (prompt.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
// User Prompt ...
|
|
var promptMessage = new ChatMessage(
|
|
ChatRole.User,
|
|
prompt
|
|
);
|
|
|
|
//
|
|
// Preparing Introduction Prompt ...
|
|
var introductionPrompt = new ChatMessage(
|
|
ChatRole.System,
|
|
configuration.GetPrompt()
|
|
);
|
|
|
|
//
|
|
List<ChatMessage> result =
|
|
[
|
|
introductionPrompt,
|
|
];
|
|
if (forcePrompt &&
|
|
prompt is not null &&
|
|
!string.IsNullOrWhiteSpace(prompt))
|
|
{
|
|
result.Add(promptMessage);
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepare a Message History List for LLM Communication based on Prompt ...
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
/// <param name="forceMessage"></param>
|
|
/// <returns></returns>
|
|
public virtual IList<ChatMessage> GetHistory(
|
|
ChatMessage message = null,
|
|
bool forceMessage = true
|
|
)
|
|
{
|
|
//
|
|
// Preparing Introduction Prompt ...
|
|
var introductionPrompt = new ChatMessage(
|
|
ChatRole.System,
|
|
configuration.GetPrompt(XAiApiConstants.XAiApiIntroductionPromptName)
|
|
);
|
|
|
|
//
|
|
List<ChatMessage> result =
|
|
[
|
|
introductionPrompt,
|
|
];
|
|
if (forceMessage &&
|
|
!message.IsNullOrDefault())
|
|
{
|
|
result.Add(message);
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Loaders ...
|
|
/// <summary>
|
|
/// Load AI Projects ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="includeDefaults"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<XAiProjectDto>> LoadProjects(
|
|
string ownerId,
|
|
string connectionId = null,
|
|
bool includeDefaults = false,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
if (ownerId.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await dataProvider.LoadProjects(
|
|
ownerId: ownerId,
|
|
fillConversations: false,
|
|
connectionId: connectionId,
|
|
fillConversationMessages: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Handle Defaults Including ...
|
|
if (includeDefaults)
|
|
{
|
|
//
|
|
// Here we have to fill Conversations of Default Project ...
|
|
var defaultProjectId = result.GetDafualtProject(ownerId);
|
|
var defaultProject = result.FirstOrDefault(x => x.Id == defaultProjectId);
|
|
result = result.Where(x => x.Id != defaultProjectId);
|
|
|
|
//
|
|
// Reading Conversations of Specified Default Project ...
|
|
var defaultProjectConversations = await dataProvider.LoadConversations(
|
|
fillMessages: false,
|
|
connectionId: connectionId,
|
|
projectId: defaultProjectId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
var defaultConversationId = defaultProjectConversations.GetDefaultConversation(
|
|
ownerId: ownerId,
|
|
projectId: defaultProjectId
|
|
);
|
|
var defaultConversation = defaultProjectConversations.First(x => x.Id == defaultConversationId);
|
|
defaultProjectConversations = defaultProjectConversations.Where(x => x.Id != defaultConversationId);
|
|
|
|
//
|
|
// Reading Default Conversations Messages ...
|
|
var defaultConversationMessages = await dataProvider.LoadMessages(
|
|
cancellationToken: cancellationToken,
|
|
conversationId: defaultConversationId
|
|
);
|
|
|
|
//
|
|
defaultConversation.Messages = [.. defaultConversationMessages.OrderByDescending(m => m.CreatedOn)];
|
|
defaultProjectConversations = [defaultConversation, .. defaultProjectConversations];
|
|
defaultProject.Conversations = [.. defaultProjectConversations];
|
|
result = [defaultProject, .. result];
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load Conversations of Specified AI Project ...
|
|
/// </summary>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<XAiConversationDto>> LoadConversations(
|
|
Guid projectId,
|
|
string connectionId = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
if (projectId.IsDefaultGuid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await dataProvider.LoadConversations(
|
|
fillMessages: false,
|
|
projectId: projectId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load Messages of Specified Conversation ...
|
|
/// </summary>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<XAiMessageDto>> LoadMessages(
|
|
Guid conversationId,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
if (conversationId.IsDefaultGuid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await dataProvider.LoadMessages(
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Queries ...
|
|
/// <summary>
|
|
/// Query Projects ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XQueryResult<XAiProjectDto>> QueryProjects(
|
|
string ownerId,
|
|
XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (ownerId.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await dataProvider.QueryProjects(
|
|
query: query,
|
|
ownerId: ownerId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query Conversations ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XQueryResult<XAiConversationDto>> QueryConversations(
|
|
string ownerId,
|
|
Guid projectId,
|
|
XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (ownerId.IsNullOrEmpty() ||
|
|
projectId.IsDefaultGuid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
// Retrieve Project ...
|
|
var project = await dataProvider.GetProject(
|
|
id: projectId,
|
|
fillConversations: false,
|
|
fillConversationMessages: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (project.IsNullOrDefault())
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Validate Owner ...
|
|
if (ownerId != project.OwnerId)
|
|
{
|
|
XException.NotAllowed.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await dataProvider.QueryConversations(
|
|
query: query,
|
|
ownerId: ownerId,
|
|
projectId: projectId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query Messages ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="query"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XQueryResult<XAiMessageDto>> QueryMessages(
|
|
string ownerId,
|
|
Guid conversationId,
|
|
XQuery query,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate ...
|
|
if (ownerId.IsNullOrEmpty() ||
|
|
conversationId.IsDefaultGuid())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
// Retrieve Conversation ...
|
|
var conversation = await dataProvider.GetConversation(
|
|
id: conversationId,
|
|
fillMessages: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (conversation.IsNullOrDefault())
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Validate Owner ...
|
|
if (ownerId != conversation.OwnerId)
|
|
{
|
|
XException.NotAllowed.Throw();
|
|
}
|
|
|
|
//
|
|
var result = await dataProvider.QueryMessages(
|
|
query: query,
|
|
ownerId: ownerId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Creators ...
|
|
/// <summary>
|
|
/// Create an AI Project ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XAiProjectDto> CreateProject(
|
|
string ownerId,
|
|
XAiProjectDto item,
|
|
string connectionId = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = await dataProvider.CreateProject(
|
|
item: item,
|
|
ownerId: ownerId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create an AI Conversation ...
|
|
/// </summary>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="item"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XAiConversationDto> CreateConversation(
|
|
string ownerId,
|
|
Guid projectId,
|
|
XAiConversationDto item,
|
|
string connectionId = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = await dataProvider.CreateConversation(
|
|
item: item,
|
|
ownerId: ownerId,
|
|
projectId: projectId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
//
|
|
#region Data Actions ...
|
|
/// <summary>
|
|
/// Ask a Question as Prompt and Retrieve Response ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="files"></param>
|
|
/// <param name="userInfo"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XAiMessageDto> AskAsync(
|
|
string prompt,
|
|
string ownerId,
|
|
Guid projectId,
|
|
Guid conversationId,
|
|
string connectionId = null,
|
|
IFormFileCollection files = null,
|
|
XUserClaimsInfoDto userInfo = null,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
validationProvider
|
|
.GroupValidationBuilder()
|
|
.AddNotEmpty(prompt)
|
|
.AddNotEmpty(ownerId)
|
|
.ValidateGroup();
|
|
|
|
//
|
|
// Check Project ...
|
|
XAiProjectDto project = null;
|
|
if (projectId.IsDefaultGuid())
|
|
{
|
|
//
|
|
project = await dataProvider.GetDefaultProject(
|
|
ownerId: ownerId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
projectId = project.Id;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
project = await dataProvider.GetProject(
|
|
id: projectId,
|
|
fillConversations: false,
|
|
fillConversationMessages: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
if (project.IsNullOrDefault())
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Check Conversation ...
|
|
XAiConversationDto conversation = null;
|
|
if (conversationId.IsDefaultGuid())
|
|
{
|
|
//
|
|
conversation = await dataProvider.GetDefaultConversation(
|
|
ownerId: ownerId,
|
|
projectId: projectId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
conversationId = conversation.Id;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
conversation = await dataProvider.GetConversation(
|
|
id: conversationId,
|
|
fillMessages: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
if (conversation.IsNullOrDefault())
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Reading Messages ...
|
|
var messages = await dataProvider.LoadMessages(
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Upload Files ...
|
|
IEnumerable<xFileService.Models.Dtos.XFileDto> uploadedFiles = null;
|
|
if (files.HasChild())
|
|
{
|
|
//
|
|
// Validate User Info ...
|
|
if (userInfo.IsNullOrDefault())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
// Upload Files ...
|
|
uploadedFiles = await fileProvider.Upload(
|
|
files: files,
|
|
userInfo: userInfo,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
|
|
//
|
|
// Handle Memory ...
|
|
IList<ChatMessage> history = await PrepareMemory(
|
|
project: project,
|
|
messages: messages,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Prepare and Add Prompt Message ...
|
|
var promptMessage = new XAiMessageDto
|
|
{
|
|
Content = prompt,
|
|
OwnerId = ownerId,
|
|
Role = XAiChatRole.User,
|
|
CreatedOn = DateTime.UtcNow,
|
|
ConversationId = conversationId,
|
|
ConversationTitle = conversation.Title
|
|
};
|
|
|
|
//
|
|
// Attached Uploaded File Id's as Meta Data ...
|
|
if (uploadedFiles.HasChild())
|
|
{
|
|
//
|
|
var fileIds = uploadedFiles.Select(f => f.Id).ToList();
|
|
promptMessage.MetaDatas = new Dictionary<string, object>
|
|
{
|
|
{ XAiServiceConstants.XMetaDataFileIdsIdentifier, fileIds }
|
|
}.ToJSON();
|
|
}
|
|
|
|
//
|
|
// Save Prompt Message ...
|
|
promptMessage = await dataProvider.AddMessage(
|
|
item: promptMessage,
|
|
connectionId: connectionId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Since Prompt Message Has Id now, we can Attach Uploaded Files to it ...
|
|
IList<AIContent> fileContents = [];
|
|
if (uploadedFiles.HasChild())
|
|
{
|
|
//
|
|
foreach (var uf in uploadedFiles)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
// Add Reference File to Prompt Message ...
|
|
await fileProvider.AddReference(
|
|
id: uf.Id,
|
|
userInfo: userInfo,
|
|
connectionId: connectionId,
|
|
forProvidedId: promptMessage.Id,
|
|
cancellationToken: cancellationToken,
|
|
providedFor: XAiServiceConstants.XAiMessageFilesIdentifier
|
|
);
|
|
|
|
//
|
|
// Handle File Content Extraction and Attach to Prompt Message ...
|
|
var fileDescriptor = await fileProvider.GetFileDescriptor(
|
|
id: uf.Id,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (fileDescriptor == null || fileDescriptor.Stream == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
var extractionResult = await fileContentExtractor.ExtractRichAsync(
|
|
fileStream: fileDescriptor.Stream,
|
|
fileName: uf.FileName ?? uf.Name,
|
|
mimeType: fileDescriptor.MIMEType,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
if (extractionResult.IsValid())
|
|
{
|
|
//
|
|
var contents = extractionResult.ToAIContents();
|
|
fileContents = [.. fileContents, .. contents];
|
|
|
|
//
|
|
// ذخیره Metadata برای ردیابی
|
|
logger.LogInformation(
|
|
"File {FileName} extracted successfully. Text: {HasText}, Images: {ImageCount}, Fallback: {UsedFallback}",
|
|
uf.FileName,
|
|
!string.IsNullOrEmpty(extractionResult.Text),
|
|
extractionResult.Images.Count,
|
|
extractionResult.UsedImageFallback
|
|
);
|
|
}
|
|
|
|
//
|
|
fileDescriptor.Stream.Dispose();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Failed to extract content from file: {FileName}", uf.FileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Converts to ChatMessage ...
|
|
var promptChatMessage = promptMessage
|
|
.ToChatMessages(fileContents);
|
|
|
|
//
|
|
// Ask Questions From LLM ...
|
|
var answer = await AskLLMAsync(
|
|
history: history,
|
|
prompt: promptChatMessage,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Prepare Answer AI Message ...
|
|
var result = new XAiMessageDto
|
|
{
|
|
Content = answer,
|
|
OwnerId = ownerId,
|
|
CreatedOn = DateTime.UtcNow,
|
|
Role = XAiChatRole.Assistant,
|
|
ConversationId = conversationId,
|
|
ConversationTitle = conversation.Title
|
|
};
|
|
result = await dataProvider.AddMessage(
|
|
item: result,
|
|
connectionId: connectionId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask a Question as Prompt and Retrieve Response as Async Enumerable ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="ownerId"></param>
|
|
/// <param name="projectId"></param>
|
|
/// <param name="conversationId"></param>
|
|
/// <param name="connectionId"></param>
|
|
/// <param name="files"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async IAsyncEnumerable<XAiResonseMessage> AskAsEnumerable(
|
|
string prompt,
|
|
string ownerId,
|
|
Guid projectId,
|
|
Guid conversationId,
|
|
string connectionId = null,
|
|
IFormFileCollection files = null,
|
|
[EnumeratorCancellation]
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
// Validate Args ...
|
|
validationProvider
|
|
.GroupValidationBuilder()
|
|
.AddNotEmpty(prompt)
|
|
.AddNotEmpty(ownerId)
|
|
.ValidateGroup();
|
|
|
|
//
|
|
// Check Project ...
|
|
XAiProjectDto project = null;
|
|
if (projectId.IsDefaultGuid())
|
|
{
|
|
//
|
|
project = await dataProvider.GetDefaultProject(
|
|
ownerId: ownerId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
projectId = project.Id;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
project = await dataProvider.GetProject(
|
|
id: projectId,
|
|
fillConversations: false,
|
|
fillConversationMessages: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
if (project.IsNullOrDefault())
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Check Conversation ...
|
|
XAiConversationDto conversation = null;
|
|
if (conversationId.IsDefaultGuid())
|
|
{
|
|
//
|
|
conversation = await dataProvider.GetDefaultConversation(
|
|
ownerId: ownerId,
|
|
projectId: projectId,
|
|
connectionId: connectionId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
conversationId = conversation.Id;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
conversation = await dataProvider.GetConversation(
|
|
id: conversationId,
|
|
fillMessages: false,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
if (conversation.IsNullOrDefault())
|
|
{
|
|
XException.NotFound.Throw();
|
|
}
|
|
|
|
//
|
|
// Reading Messages ...
|
|
var messages = await dataProvider.LoadMessages(
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// TODO: Parse Files ...
|
|
// for Attached them into Prompt Message ...
|
|
|
|
//
|
|
// Handle Memory ...
|
|
IList<ChatMessage> history = await PrepareMemory(
|
|
project: project,
|
|
messages: messages,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Prepare and Add Prompt Message ...
|
|
var promptMessage = new XAiMessageDto
|
|
{
|
|
Content = prompt,
|
|
OwnerId = ownerId,
|
|
Role = XAiChatRole.User,
|
|
CreatedOn = DateTime.UtcNow,
|
|
ConversationId = conversationId,
|
|
};
|
|
promptMessage = await dataProvider.AddMessage(
|
|
item: promptMessage,
|
|
connectionId: connectionId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
promptMessage = await dataProvider.Enrich(
|
|
source: promptMessage,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Attach Uploaded Files ...
|
|
var promptChatMessage = promptMessage
|
|
.ToChatMessages();
|
|
history.Add(promptChatMessage);
|
|
|
|
//
|
|
// Ask Questions From LLM ...
|
|
using var client = GetClient();
|
|
var enumerable = client.GetStreamingResponseAsync(
|
|
options: null,
|
|
messages: history
|
|
);
|
|
|
|
//
|
|
long sequence = 1;
|
|
var answer = string.Empty;
|
|
await foreach (var res in enumerable)
|
|
{
|
|
//
|
|
// Cancellation Token ...
|
|
if (cancellationToken.IsCancellationRequested)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
//
|
|
var resp = new XAiResonseMessage
|
|
{
|
|
OwnerId = ownerId,
|
|
Content = res.Text,
|
|
Sequence = sequence,
|
|
Role = XAiChatRole.System,
|
|
RespondTo = promptMessage.Id,
|
|
RespondedOn = DateTime.UtcNow,
|
|
};
|
|
|
|
//
|
|
sequence++;
|
|
answer += res.Text;
|
|
|
|
//
|
|
yield return resp;
|
|
}
|
|
|
|
//
|
|
// Prepare Answer AI Message ...
|
|
var result = new XAiMessageDto
|
|
{
|
|
Content = answer,
|
|
OwnerId = ownerId,
|
|
Role = XAiChatRole.System,
|
|
CreatedOn = DateTime.UtcNow,
|
|
ConversationId = conversationId
|
|
};
|
|
result = await dataProvider.AddMessage(
|
|
item: result,
|
|
connectionId: connectionId,
|
|
conversationId: conversationId,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Base Dispose Action ...
|
|
/// </summary>
|
|
/// <exception cref="NotImplementedException"></exception>
|
|
public virtual void Dispose()
|
|
{ }
|
|
|
|
//
|
|
#region Private ...
|
|
/// <summary>
|
|
/// Implementing Memory Strategy here ...
|
|
/// </summary>
|
|
/// <param name="project"></param>
|
|
/// <param name="messages"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
private async Task<IList<ChatMessage>> PrepareMemory(
|
|
XAiProjectDto project,
|
|
IEnumerable<XAiMessageDto> messages,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = new List<ChatMessage>();
|
|
|
|
//
|
|
// Retrieve Introduction Prompt ...
|
|
var introductionPrompt = configuration.GetPrompt(XAiApiConstants.XAiApiIntroductionPromptName);
|
|
if (introductionPrompt.IsNullOrEmpty())
|
|
{
|
|
XException.InvalidData.Throw();
|
|
}
|
|
|
|
//
|
|
var introductionMessage = new ChatMessage(
|
|
role: ChatRole.System,
|
|
content: introductionPrompt
|
|
);
|
|
|
|
//
|
|
result.Add(introductionMessage);
|
|
|
|
//
|
|
if (!project.IsNullOrDefault() &&
|
|
!project.Prompt.IsNullOrEmpty())
|
|
{
|
|
//
|
|
// TODO: Handle Project Files Here ...
|
|
var projectMessage = new ChatMessage(
|
|
role: ChatRole.System,
|
|
contents: [new TextContent(project.Prompt)]
|
|
);
|
|
result.Add(projectMessage);
|
|
}
|
|
|
|
//
|
|
if (messages.HasChild())
|
|
{
|
|
//
|
|
// TODO: Here we can Implement Memory Management based on
|
|
// Last nth of Messages, or Summarization of Messages, etc ...
|
|
result = [..result, ..messages
|
|
.OrderBy(m => m.CreatedOn)
|
|
.Select(m => m.ToChatMessages())];
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ask Quetion from LLM ...
|
|
/// </summary>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="history"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
private async Task<string> AskLLMAsync(
|
|
ChatMessage prompt,
|
|
IList<ChatMessage> history,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
if (prompt.IsNullOrDefault())
|
|
{
|
|
XException.InvalidArgs.Throw();
|
|
}
|
|
|
|
//
|
|
history.Add(prompt);
|
|
using var client = GetClient();
|
|
var response = await client.GetResponseAsync(
|
|
messages: history,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
// Validate Response ...
|
|
if (!response.IsValid())
|
|
{
|
|
//
|
|
// Dispose Client ...
|
|
client.Dispose();
|
|
XException.ActionFailed.Throw();
|
|
}
|
|
|
|
//
|
|
// Retrieve Response Text ...
|
|
var result = response.Text;
|
|
|
|
//
|
|
return result;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\XDefaultAiService.cs
|
|
|
|
using xAiApi.Constants;
|
|
using xAiApi.Interfaces;
|
|
using xCommons.Providers;
|
|
using xAiModels.Interfaces;
|
|
using xAiApi.Configurations;
|
|
using xFileService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using xAiApi.Interfaces.Extractors;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
public class XDefaultAiService : XAIServiceBase, IXDefaultAiService
|
|
{
|
|
public XDefaultAiService(
|
|
IXFileProvider fileProvider,
|
|
IXAiDataProvider dataProvider,
|
|
ILogger<XDefaultAiService> logger,
|
|
XAiApiConfiguration configuration,
|
|
XValidationProvider validationProvider,
|
|
IXFileContentExtractor fileContentExtractor
|
|
) : base(
|
|
fileProvider,
|
|
dataProvider,
|
|
logger,
|
|
configuration,
|
|
validationProvider,
|
|
fileContentExtractor,
|
|
XAiApiConstants.XAiDefaultModelName
|
|
)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\XDefaultEmbeddingService.cs
|
|
|
|
using xAiApi.Constants;
|
|
using xAiApi.Interfaces;
|
|
using xAiModels.Interfaces;
|
|
using xAiApi.Configurations;
|
|
using xFileService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using xAiApi.Interfaces.Extractors;
|
|
using xCommons.Providers;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
public class XDefaultEmbeddingService : XAiEmbeddingServiceBase, IXDefaultEmbeddingService
|
|
{
|
|
public XDefaultEmbeddingService(
|
|
IXFileProvider fileProvider,
|
|
IXAiDataProvider dataProvider,
|
|
XAiApiConfiguration configuration,
|
|
XValidationProvider validationProvider,
|
|
ILogger<XDefaultEmbeddingService> logger,
|
|
IXFileContentExtractor fileContentExtractor
|
|
) : base(
|
|
fileProvider,
|
|
dataProvider,
|
|
configuration,
|
|
validationProvider,
|
|
logger,
|
|
fileContentExtractor,
|
|
XAiApiConstants.XAiEmbeddingModelName
|
|
)
|
|
{ }
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\XDefaultThinkingAiService.cs
|
|
|
|
using xAiApi.Constants;
|
|
using xAiApi.Interfaces;
|
|
using xCommons.Providers;
|
|
using xAiModels.Interfaces;
|
|
using xAiApi.Configurations;
|
|
using Microsoft.Extensions.AI;
|
|
using xFileService.Interfaces;
|
|
using Microsoft.Extensions.Logging;
|
|
using xAiApi.Interfaces.Extractors;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
public class XDefaultThinkingAiService : XAIServiceBase, IXDefaultThinkingAiService
|
|
{
|
|
public XDefaultThinkingAiService(
|
|
IXFileProvider fileProvider,
|
|
IXAiDataProvider dataProvider,
|
|
ILogger<XAIServiceBase> logger,
|
|
XAiApiConfiguration configuration,
|
|
XValidationProvider validationProvider,
|
|
IXFileContentExtractor fileContentExtractor,
|
|
ReasoningEffort effort = ReasoningEffort.Medium,
|
|
ReasoningOutput output = ReasoningOutput.Full
|
|
) : base(
|
|
fileProvider,
|
|
dataProvider,
|
|
logger,
|
|
configuration,
|
|
validationProvider,
|
|
fileContentExtractor,
|
|
XAiApiConstants.XAiDefaultThinkingModelName
|
|
)
|
|
{
|
|
//
|
|
// Preparing Reasoning ...
|
|
Options.Reasoning = new ReasoningOptions
|
|
{
|
|
Output = output,
|
|
Effort = effort,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\Extractors\XAudioFileContentExtractor.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using Whisper.net;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using System.Threading.Tasks;
|
|
using xAiApi.Interfaces.Extractors;
|
|
|
|
namespace xAiApi.Providers.Extractors
|
|
{
|
|
/// <summary>
|
|
/// Extracts text content from Audio files ...
|
|
/// </summary>
|
|
public class XAudioFileContentExtractor : IXAudioFileContentExtractor
|
|
{
|
|
/// <summary>
|
|
/// Supported MIME Types ...
|
|
/// </summary>
|
|
private static readonly string[] SupportedMimeTypes =
|
|
[
|
|
"audio/mpeg",
|
|
"audio/mp3",
|
|
"audio/wav",
|
|
"audio/wave",
|
|
"audio/ogg",
|
|
"audio/m4a",
|
|
"audio/mp4",
|
|
"audio/webm"
|
|
];
|
|
|
|
private readonly string language;
|
|
private readonly string whisperModelPath;
|
|
|
|
public XAudioFileContentExtractor(
|
|
string whisperModelPath = "Models/ggml-base.bin",
|
|
string language = "fa"
|
|
)
|
|
{
|
|
//
|
|
this.language = language;
|
|
this.whisperModelPath = whisperModelPath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
public bool CanExtract(string mimeType)
|
|
{
|
|
//
|
|
return SupportedMimeTypes.Contains(
|
|
mimeType?.ToLowerInvariant() ?? string.Empty
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
public async Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = await ExtractRichAsync(
|
|
fileStream,
|
|
"audio",
|
|
mimeType,
|
|
cancellationToken
|
|
);
|
|
|
|
//
|
|
return result.AudioTranscript;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = new XFileExtractionResult
|
|
{
|
|
FileName = fileName,
|
|
MimeType = mimeType
|
|
};
|
|
|
|
//
|
|
try
|
|
{
|
|
//
|
|
using var memoryStream = new MemoryStream();
|
|
await fileStream.CopyToAsync(memoryStream, cancellationToken);
|
|
memoryStream.Position = 0;
|
|
|
|
//
|
|
using var wavStream = await ConvertToWavAsync(
|
|
memoryStream,
|
|
cancellationToken
|
|
);
|
|
|
|
//
|
|
using var factory = WhisperFactory.FromPath(whisperModelPath);
|
|
using var processor = factory.CreateBuilder()
|
|
.WithLanguage(language)
|
|
.Build();
|
|
|
|
//
|
|
var segments = new System.Text.StringBuilder();
|
|
await foreach (var segment in processor.ProcessAsync(wavStream, cancellationToken))
|
|
{
|
|
segments.Append(segment.Text);
|
|
}
|
|
|
|
//
|
|
result.AudioTranscript = segments.ToString().Trim();
|
|
result.Text = result.AudioTranscript;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.ErrorMessage = $"Audio extraction failed: {ex.Message}";
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Audio Format Converting ...
|
|
/// </summary>
|
|
private async Task<Stream> ConvertToWavAsync(
|
|
Stream inputStream,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
//
|
|
// TODO: Complete this ...
|
|
return await Task.FromResult(inputStream);
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\Extractors\XDocxFileContentExtractor.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using System.Threading.Tasks;
|
|
using xAiApi.Interfaces.Extractors;
|
|
using DocumentFormat.OpenXml.Drawing;
|
|
using DocumentFormat.OpenXml.Packaging;
|
|
|
|
namespace xAiApi.Providers.Extractors
|
|
{
|
|
/// <summary>
|
|
/// Extracts text content from DOCX files using DocumentFormat.OpenXml ...
|
|
/// </summary>
|
|
public class XDocxFileContentExtractor : IXDocxFileContentExtractor
|
|
{
|
|
/// <summary>
|
|
/// Supported MIME Types ...
|
|
/// </summary>
|
|
private static readonly string[] SupportedMimeTypes =
|
|
[
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
];
|
|
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
public bool CanExtract(string mimeType)
|
|
{
|
|
//
|
|
return SupportedMimeTypes.Contains(
|
|
mimeType?.ToLowerInvariant() ?? string.Empty
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
public async Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
using var memoryStream = new MemoryStream();
|
|
await fileStream.CopyToAsync(memoryStream, cancellationToken);
|
|
memoryStream.Position = 0;
|
|
|
|
//
|
|
var stringBuilder = new StringBuilder();
|
|
|
|
//
|
|
using (var wordDocument = WordprocessingDocument.Open(memoryStream, false))
|
|
{
|
|
//
|
|
var body = wordDocument.MainDocumentPart?.Document?.Body;
|
|
if (body != null)
|
|
{
|
|
//
|
|
var paragraphs = body.Elements<Paragraph>();
|
|
foreach (var paragraph in paragraphs)
|
|
{
|
|
//
|
|
var text = paragraph.InnerText?.Trim();
|
|
if (!string.IsNullOrEmpty(text))
|
|
{
|
|
stringBuilder.AppendLine(text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var content = await ExtractAsync(
|
|
mimeType: mimeType,
|
|
fileStream: fileStream,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
var result = new XFileExtractionResult
|
|
{
|
|
Text = content,
|
|
FileName = fileName,
|
|
MimeType = mimeType
|
|
};
|
|
|
|
//
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\Extractors\XExcelFileContentExtractor.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using ExcelDataReader;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using xAiApi.Interfaces.Extractors;
|
|
using xAiModels.Models;
|
|
|
|
namespace xAiApi.Providers.Extractors
|
|
{
|
|
public class XExcelFileContentExtractor : IXExcelFileContentExtractor
|
|
{
|
|
/// <summary>
|
|
/// Supported MIME Types ...
|
|
/// </summary>
|
|
private static readonly string[] SupportedMimeTypes =
|
|
[
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx
|
|
"application/vnd.ms-excel" // .xls
|
|
];
|
|
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
public bool CanExtract(string mimeType)
|
|
{
|
|
//
|
|
return SupportedMimeTypes.Contains(
|
|
mimeType?.ToLowerInvariant() ?? string.Empty
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
public async Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
|
|
//
|
|
using var memoryStream = new MemoryStream();
|
|
await fileStream.CopyToAsync(memoryStream, cancellationToken);
|
|
memoryStream.Position = 0;
|
|
|
|
//
|
|
var stringBuilder = new StringBuilder();
|
|
|
|
//
|
|
using (var reader = ExcelReaderFactory.CreateReader(memoryStream))
|
|
{
|
|
//
|
|
var result = reader.AsDataSet();
|
|
foreach (DataTable table in result.Tables)
|
|
{
|
|
//
|
|
stringBuilder.AppendLine($"--- Sheet: {table.TableName} ---");
|
|
|
|
//
|
|
var headers = new string[table.Columns.Count];
|
|
for (int i = 0; i < table.Columns.Count; i++)
|
|
{
|
|
headers[i] = table.Columns[i].ColumnName;
|
|
}
|
|
stringBuilder.AppendLine(string.Join(" | ", headers));
|
|
stringBuilder.AppendLine(new string('-', 50));
|
|
|
|
//
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
//
|
|
var rowValues = new string[row.ItemArray.Length];
|
|
for (int i = 0; i < row.ItemArray.Length; i++)
|
|
{
|
|
rowValues[i] = row.ItemArray[i]?.ToString()?.Trim() ?? string.Empty;
|
|
}
|
|
stringBuilder.AppendLine(string.Join(" | ", rowValues));
|
|
}
|
|
stringBuilder.AppendLine();
|
|
}
|
|
}
|
|
|
|
//
|
|
return stringBuilder.ToString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var content = await ExtractAsync(
|
|
mimeType: mimeType,
|
|
fileStream: fileStream,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
var result = new XFileExtractionResult
|
|
{
|
|
Text = content,
|
|
FileName = fileName,
|
|
MimeType = mimeType
|
|
};
|
|
|
|
//
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\Extractors\XFileContentExtractor.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Reflection;
|
|
using xCommons.Extensions;
|
|
using xExceptions.Constants;
|
|
using System.Threading.Tasks;
|
|
using System.Collections.Generic;
|
|
using xAiApi.Interfaces.Extractors;
|
|
using xAiModels.Models;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
/// <summary>
|
|
/// Composite extractor that delegates to appropriate extractor
|
|
/// based on MIME type ...
|
|
/// </summary>
|
|
public class XFileContentExtractor : IXFileContentExtractor
|
|
{
|
|
//
|
|
private readonly IList<IXFileContentExtractor> extractors = [];
|
|
|
|
public XFileContentExtractor()
|
|
{
|
|
//
|
|
var path = Path.Combine(AppContext.BaseDirectory);
|
|
var files = Directory.GetFiles(path, "*.dll");
|
|
foreach (var dll in files)
|
|
{
|
|
//
|
|
// Retrieve DLL Assembly ...
|
|
var assembly = Assembly.LoadFrom(dll);
|
|
|
|
//
|
|
// Extract Registerar Types ...
|
|
var extractorTypes = assembly
|
|
.GetExportedTypes()
|
|
.Where(t =>
|
|
!t.IsAbstract &&
|
|
typeof(IXFileContentExtractor).IsAssignableFrom(t));
|
|
foreach (var extractorType in extractorTypes)
|
|
{
|
|
//
|
|
// Ignore XFileContentExtractor ...
|
|
if (extractorType.FullName.Contains(nameof(XFileContentExtractor)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Create Instance of Service ...
|
|
var instance = (IXFileContentExtractor)Activator.CreateInstance(extractorType);
|
|
if (!instance.IsNull())
|
|
{
|
|
//
|
|
// Add Registerar Instance to Collection ...
|
|
extractors.Add(instance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
public bool CanExtract(string mimeType)
|
|
{
|
|
return extractors.Any(e => e.CanExtract(mimeType));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
public async Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var extractor = extractors
|
|
.FirstOrDefault(e => e.CanExtract(mimeType));
|
|
if (extractor.IsNull())
|
|
{
|
|
//
|
|
XException.NotAllowed.Throw(
|
|
$"Unsupported file type: {mimeType}"
|
|
);
|
|
}
|
|
|
|
//
|
|
var result = await extractor
|
|
.ExtractAsync(
|
|
mimeType: mimeType,
|
|
fileStream: fileStream,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var extractor = extractors
|
|
.FirstOrDefault(e => e.CanExtract(mimeType));
|
|
if (extractor.IsNull())
|
|
{
|
|
//
|
|
XException.NotAllowed.Throw(
|
|
$"Unsupported file type: {mimeType}"
|
|
);
|
|
}
|
|
|
|
//
|
|
var result = await extractor
|
|
.ExtractRichAsync(
|
|
mimeType: mimeType,
|
|
fileName: fileName,
|
|
fileStream: fileStream,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\Extractors\XImageFileContentExtractor.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using System.Threading.Tasks;
|
|
using xAiApi.Interfaces.Extractors;
|
|
|
|
namespace xAiApi.Providers.Extractors
|
|
{
|
|
public class XImageFileContentExtractor : IXImageFileContentExtractor
|
|
{
|
|
/// <summary>
|
|
/// Supported MIME Types ...
|
|
/// </summary>
|
|
private static readonly string[] SupportedMimeTypes =
|
|
[
|
|
"image/png",
|
|
"image/jpeg",
|
|
"image/jpg",
|
|
"image/gif",
|
|
"image/webp",
|
|
"image/bmp"
|
|
];
|
|
|
|
/// <summary>
|
|
/// آیا OCR فعال است (برای مدلهای غیر Vision) ...
|
|
/// </summary>
|
|
private readonly bool _enableOcrFallback;
|
|
|
|
public XImageFileContentExtractor(bool enableOcrFallback = true)
|
|
{
|
|
_enableOcrFallback = enableOcrFallback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
public bool CanExtract(string mimeType)
|
|
{
|
|
//
|
|
return SupportedMimeTypes.Contains(
|
|
mimeType?.ToLowerInvariant() ?? string.Empty
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
public async Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = await ExtractRichAsync(
|
|
fileStream,
|
|
"image",
|
|
mimeType,
|
|
cancellationToken
|
|
);
|
|
|
|
//
|
|
return result.Text;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = new XFileExtractionResult
|
|
{
|
|
FileName = fileName,
|
|
MimeType = mimeType
|
|
};
|
|
|
|
//
|
|
using var memoryStream = new MemoryStream();
|
|
await fileStream.CopyToAsync(memoryStream, cancellationToken);
|
|
var imageBytes = memoryStream.ToArray();
|
|
|
|
//
|
|
result.Images.Add(new XExtractedImage
|
|
{
|
|
Bytes = imageBytes,
|
|
MimeType = mimeType,
|
|
Description = $"Attached image: {fileName}"
|
|
});
|
|
|
|
// OCR Fallback برای مدلهای متنی
|
|
if (_enableOcrFallback)
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var ocrText = await PerformOcrAsync(imageBytes, cancellationToken);
|
|
if (!string.IsNullOrWhiteSpace(ocrText))
|
|
{
|
|
result.Text = ocrText;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// OCR failed, but image is still available for Vision
|
|
}
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Do OCR on Image for Text Extraction ...
|
|
/// </summary>
|
|
private async Task<string> PerformOcrAsync(
|
|
byte[] imageBytes,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
//
|
|
// TODO: Complete this ...
|
|
return await Task.FromResult(string.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\Extractors\XPdfFileContentExtractor.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using PdfiumViewer;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using System.Threading.Tasks;
|
|
using xAiApi.Interfaces.Extractors;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
/// <summary>
|
|
/// Extracts content from PDF files using PdfPig ...
|
|
/// </summary>
|
|
public class XPdfFileContentExtractor : IXPdfFileContentExtractor
|
|
{
|
|
/// <summary>
|
|
/// Supported MIME Types ...
|
|
/// </summary>
|
|
private static readonly string[] SupportedMimeTypes =
|
|
[
|
|
"application/pdf"
|
|
];
|
|
|
|
/// <summary>
|
|
/// Minimum Length for Fallback Using Images ...
|
|
/// </summary>
|
|
private const int MinTextLengthThreshold = 50;
|
|
|
|
/// <summary>
|
|
/// Max Pages for Falling Back Using Images ...
|
|
/// </summary>
|
|
private const int MaxPagesForImageFallback = 10;
|
|
|
|
/// <summary>
|
|
/// Default DPI for Rendering Images ...
|
|
/// </summary>
|
|
private const int RenderDpi = 150;
|
|
|
|
/// <summary>
|
|
/// Max Rendered Image Width ...
|
|
/// </summary>
|
|
private const int MaxImageWidth = 2000;
|
|
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
public bool CanExtract(string mimeType)
|
|
{
|
|
//
|
|
return SupportedMimeTypes.Contains(
|
|
mimeType?.ToLowerInvariant() ?? string.Empty
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
public async Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = await ExtractRichAsync(
|
|
fileStream,
|
|
"document.pdf",
|
|
mimeType,
|
|
cancellationToken
|
|
);
|
|
|
|
//
|
|
return result.Text;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var result = new XFileExtractionResult
|
|
{
|
|
FileName = fileName,
|
|
MimeType = mimeType
|
|
};
|
|
|
|
//
|
|
using var memoryStream = new MemoryStream();
|
|
await fileStream.CopyToAsync(memoryStream, cancellationToken);
|
|
memoryStream.Position = 0;
|
|
|
|
//
|
|
var extractedText = await ExtractTextAsync(
|
|
memoryStream,
|
|
cancellationToken
|
|
);
|
|
if (IsTextSufficient(extractedText))
|
|
{
|
|
// PDF متنی است
|
|
result.Text = extractedText;
|
|
}
|
|
else
|
|
{
|
|
// PDF اسکنشده است - Fallback به تصویر
|
|
memoryStream.Position = 0;
|
|
result = await FallbackToImageExtractionAsync(
|
|
memoryStream,
|
|
fileName,
|
|
mimeType,
|
|
cancellationToken
|
|
);
|
|
result.UsedImageFallback = true;
|
|
}
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract Text From PDF Files using Common way ...
|
|
/// </summary>
|
|
/// <param name="pdfStream"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
private async Task<string> ExtractTextAsync(
|
|
Stream pdfStream,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
//
|
|
var result = await Task.Run(() =>
|
|
{
|
|
//
|
|
var sb = new StringBuilder();
|
|
using var document = UglyToad.PdfPig.PdfDocument.Open(pdfStream);
|
|
foreach (var page in document.GetPages())
|
|
{
|
|
//
|
|
var text = page.Text?.Trim();
|
|
if (!string.IsNullOrEmpty(text))
|
|
{
|
|
//
|
|
sb.AppendLine(text);
|
|
sb.AppendLine();
|
|
}
|
|
}
|
|
|
|
//
|
|
return sb.ToString();
|
|
}, cancellationToken);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check Extracted Content is Sufficient or not ...
|
|
/// if not try to Falling Back using Images ...
|
|
/// </summary>
|
|
private bool IsTextSufficient(string text)
|
|
{
|
|
//
|
|
if (string.IsNullOrWhiteSpace(text))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// حذف فاصلهها و بررسی طول
|
|
var cleanText = new string(text.Where(c => !char.IsWhiteSpace(c)).ToArray());
|
|
|
|
//
|
|
return cleanText.Length >= MinTextLengthThreshold;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fallback Converts PDF Pages to Images ...
|
|
/// </summary>
|
|
private async Task<XFileExtractionResult> FallbackToImageExtractionAsync(
|
|
Stream pdfStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
//
|
|
var result = new XFileExtractionResult
|
|
{
|
|
FileName = fileName,
|
|
MimeType = mimeType,
|
|
UsedImageFallback = true
|
|
};
|
|
|
|
//
|
|
await Task.Run(() =>
|
|
{
|
|
//
|
|
try
|
|
{
|
|
//
|
|
using var document = PdfDocument.Load(pdfStream);
|
|
|
|
//
|
|
var pageCount = Math.Min(
|
|
document.PageCount,
|
|
MaxPagesForImageFallback
|
|
);
|
|
|
|
//
|
|
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++)
|
|
{
|
|
//
|
|
if (cancellationToken.IsCancellationRequested)
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
try
|
|
{
|
|
//
|
|
var pageSize = document.PageSizes[pageIndex];
|
|
|
|
//
|
|
var scaleFactor = RenderDpi / 72.0;
|
|
var renderWidth = (int)Math.Ceiling(pageSize.Width * scaleFactor);
|
|
var renderHeight = (int)Math.Ceiling(pageSize.Height * scaleFactor);
|
|
|
|
//
|
|
if (renderWidth > MaxImageWidth)
|
|
{
|
|
//
|
|
var ratio = (double)MaxImageWidth / renderWidth;
|
|
renderWidth = MaxImageWidth;
|
|
renderHeight = (int)(renderHeight * ratio);
|
|
}
|
|
|
|
//
|
|
using var bitmap = document.Render(
|
|
dpiX: RenderDpi,
|
|
dpiY: RenderDpi,
|
|
page: pageIndex,
|
|
width: renderWidth,
|
|
forPrinting: false,
|
|
height: renderHeight
|
|
);
|
|
|
|
//
|
|
byte[] imageBytes;
|
|
using (var memoryStream = new MemoryStream())
|
|
{
|
|
//
|
|
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
|
|
imageBytes = memoryStream.ToArray();
|
|
}
|
|
|
|
//
|
|
result.Images.Add(new XExtractedImage
|
|
{
|
|
Bytes = imageBytes,
|
|
MimeType = "image/png",
|
|
PageNumber = pageIndex + 1,
|
|
Description = $"Page {pageIndex + 1} of {fileName} ({renderWidth}x{renderHeight})"
|
|
});
|
|
}
|
|
catch (Exception)
|
|
{ }
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
result.ErrorMessage = $"PDF image extraction failed: {ex.Message}";
|
|
}
|
|
}, cancellationToken);
|
|
|
|
//
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\xAiApi\Providers\Extractors\XPlainTextFileContentExtractor.cs
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using xAiModels.Models;
|
|
using System.Threading.Tasks;
|
|
using xAiApi.Interfaces.Extractors;
|
|
|
|
namespace xAiApi.Providers
|
|
{
|
|
/// <summary>
|
|
/// Extracts content from plain text files (txt, md, csv, json, xml) ...
|
|
/// </summary>
|
|
public class XPlainTextFileContentExtractor : IXPlainTextFileContentExtractor
|
|
{
|
|
/// <summary>
|
|
/// Supported MIME Types ...
|
|
/// </summary>
|
|
private static readonly string[] SupportedMimeTypes =
|
|
[
|
|
"text/plain",
|
|
"text/markdown",
|
|
"text/csv",
|
|
"text/html",
|
|
"text/xml",
|
|
"application/json",
|
|
"application/xml"
|
|
];
|
|
|
|
/// <summary>
|
|
/// Check if this extractor supports the specified MIME type ...
|
|
/// </summary>
|
|
public bool CanExtract(string mimeType)
|
|
{
|
|
//
|
|
return SupportedMimeTypes.Contains(
|
|
mimeType?.ToLowerInvariant() ?? string.Empty
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract text content from file stream ...
|
|
/// </summary>
|
|
public async Task<string> ExtractAsync(
|
|
Stream fileStream,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
using var reader = new StreamReader(fileStream);
|
|
return await reader.ReadToEndAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extract content from stream as Rich Result ...
|
|
/// </summary>
|
|
/// <param name="fileStream"></param>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="mimeType"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
public async Task<XFileExtractionResult> ExtractRichAsync(
|
|
Stream fileStream,
|
|
string fileName,
|
|
string mimeType,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
//
|
|
var content = await ExtractAsync(
|
|
mimeType: mimeType,
|
|
fileStream: fileStream,
|
|
cancellationToken: cancellationToken
|
|
);
|
|
|
|
//
|
|
var result = new XFileExtractionResult
|
|
{
|
|
Text = content,
|
|
FileName = fileName,
|
|
MimeType = mimeType
|
|
};
|
|
|
|
//
|
|
return result;
|
|
}
|
|
}
|
|
}
|