Compare commits
8
Commits
fc679fe294
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70c2d5a649 | ||
|
|
7cad889cd0 | ||
|
|
c30d93be0b | ||
|
|
4521a53734 | ||
|
|
f32972b55f | ||
|
|
aac3eb92c1 | ||
|
|
efaf71d962 | ||
|
|
383e42a8d2 |
@@ -1,3 +1,4 @@
|
||||
using xAiApi.Models;
|
||||
using xAiModels.Models;
|
||||
|
||||
namespace xAiApi.Configurations
|
||||
@@ -13,5 +14,10 @@ namespace xAiApi.Configurations
|
||||
/// Allowed Prompts to Use ...
|
||||
/// </summary>
|
||||
public XAiPromptDescriptor[] Prompts { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// OCR Configuration ...
|
||||
/// </summary>
|
||||
public XAiOCRDescriptor OCR { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,15 @@ namespace xAiApi.Constants
|
||||
|
||||
//
|
||||
public const string XDILogTag = "XAiApi";
|
||||
|
||||
//
|
||||
public const string XAiApiIntroductionPromptName = "Introduction";
|
||||
public const string XAiApiContentExtractionPromptName = "ContentExtraction";
|
||||
|
||||
//
|
||||
public const string XAiDefaultModelName = "Gemma";
|
||||
public const string XAiEmbeddingModelName = "Embed";
|
||||
public const string XAiDefaultVisionModelName = "Qwen";
|
||||
public const string XAiDefaultThinkingModelName = "Qwen";
|
||||
public const string XAiEmbeddingPromptName = "Embedding";
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using xModels.Dtos;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using xAiModels.Models;
|
||||
@@ -17,6 +18,7 @@ using xIdentityService.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using xIdentityService.Controllers;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Linq;
|
||||
|
||||
namespace xAiApi.Controllers
|
||||
{
|
||||
@@ -164,14 +166,54 @@ namespace xAiApi.Controllers
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Data Provided Actions ...
|
||||
#region Loaders ...
|
||||
/// <summary>
|
||||
/// Load AI Projects ...
|
||||
/// Retrieve AI Projects ...
|
||||
/// </summary>
|
||||
/// <param name="includeDefaults"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("LoadProjects")]
|
||||
[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
|
||||
)
|
||||
{
|
||||
@@ -180,13 +222,234 @@ namespace xAiApi.Controllers
|
||||
{
|
||||
//
|
||||
// Validate ...
|
||||
if (projectId.IsDefaultGuid())
|
||||
{
|
||||
XException.InvalidArgs.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
var userInfo = await GetUserInfo();
|
||||
var connectionId = GetConnectionId();
|
||||
|
||||
//
|
||||
var result = await aiService.LoadProjects(
|
||||
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
|
||||
@@ -203,6 +466,121 @@ namespace xAiApi.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 Content Extraction ...
|
||||
/// <summary>
|
||||
/// Extract Files Content ...
|
||||
/// </summary>
|
||||
/// <param name="files"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("ExtractContent")]
|
||||
[Consumes("multipart/form-data")]
|
||||
public async Task<ActionResult<string>> ExtractContent(
|
||||
[FromForm] IFormFileCollection files,
|
||||
[FromForm] string extractors = null,
|
||||
CancellationToken cancellationToken = default
|
||||
) {
|
||||
//
|
||||
try
|
||||
{
|
||||
//
|
||||
// Validate ...
|
||||
if (!files.HasChild())
|
||||
{
|
||||
XException.InvalidArgs.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
var userInfo = await GetUserInfo();
|
||||
var connectionId = GetConnectionId();
|
||||
|
||||
//
|
||||
string[] extractorArr = null;
|
||||
if (!extractors.IsNullOrEmpty())
|
||||
{
|
||||
extractorArr = [.. extractors.ParseListString<string>()];
|
||||
}
|
||||
|
||||
//
|
||||
var result = await aiService.ExtractContent(
|
||||
files: files,
|
||||
extractors: extractorArr,
|
||||
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>
|
||||
@@ -330,6 +708,56 @@ namespace xAiApi.Controllers
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,20 @@
|
||||
using System;
|
||||
using OpenAI;
|
||||
using System.Linq;
|
||||
using OllamaSharp;
|
||||
using System.Net.Http;
|
||||
using xAiApi.Constants;
|
||||
using xAiModels.Models;
|
||||
using System.Threading;
|
||||
using System.ClientModel;
|
||||
using xCommons.Extensions;
|
||||
using xAiModels.Constants;
|
||||
using xAiModels.Extensions;
|
||||
using xAiApi.Configurations;
|
||||
using xExceptions.Constants;
|
||||
using Microsoft.Extensions.AI;
|
||||
using System.Collections.Generic;
|
||||
using System.ClientModel.Primitives;
|
||||
|
||||
namespace xAiApi.Extensions
|
||||
{
|
||||
@@ -176,5 +186,107 @@ namespace xAiApi.Extensions
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provide HttpClient for Common Usages ...
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
public static HttpClient GetHttpClient(this string source)
|
||||
{
|
||||
//
|
||||
var result = new HttpClient
|
||||
{
|
||||
//
|
||||
// Disable timeout completely (not recommended for production)
|
||||
Timeout = Timeout.InfiniteTimeSpan,
|
||||
|
||||
//
|
||||
BaseAddress = source.IsNullOrEmpty()
|
||||
? null
|
||||
: new Uri(source),
|
||||
|
||||
//
|
||||
// OR set a very long timeout (e.g., 30 minutes for large file uploads)
|
||||
// Timeout = TimeSpan.FromMinutes(30)
|
||||
};
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instance and Retrieve Chat Client ...
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
public static IChatClient GetClient(this XAiModelDescriptor source)
|
||||
{
|
||||
//
|
||||
// Try to Initialize LLm ...
|
||||
var model = source.LLM;
|
||||
var apiKey = source.ApiKey;
|
||||
var url = new Uri(source.Url);
|
||||
var httpClient = source.Url.GetHttpClient();
|
||||
|
||||
//
|
||||
IChatClient result = null;
|
||||
switch (source.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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
public interface IXAudioFileContentExtractor : IXFileContentExtractorBase
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
public interface IXDocxFileContentExtractor : IXFileContentExtractorBase
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
public interface IXExcelFileContentExtractor : IXFileContentExtractorBase
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using xAiModels.Models;
|
||||
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
/// <summary>
|
||||
/// a Base Service for Extracting File Content ...
|
||||
/// </summary>
|
||||
public interface IXFileContentExtractorBase
|
||||
{
|
||||
/// <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
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// a Service for Extracting File Content ...
|
||||
/// </summary>
|
||||
public interface IXFileContentExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if this extractor supports the specified MIME type ...
|
||||
/// </summary>
|
||||
/// <param name="mimeType"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <returns></returns>
|
||||
bool CanExtract(
|
||||
string mimeType,
|
||||
string[] extractors = null
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Extract text content from file stream ...
|
||||
/// </summary>
|
||||
/// <param name="fileStream"></param>
|
||||
/// <param name="mimeType"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<string> ExtractAsync(
|
||||
Stream fileStream,
|
||||
string mimeType,
|
||||
string[] extractors = null,
|
||||
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="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<XFileExtractionResult> ExtractRichAsync(
|
||||
Stream fileStream,
|
||||
string fileName,
|
||||
string mimeType,
|
||||
string[] extractors = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
public interface IXImageFileContentExtractor : IXFileContentExtractorBase
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
public interface IXPdfFileContentExtractor : IXFileContentExtractorBase
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
public interface IXPlainTextFileContentExtractor : IXFileContentExtractorBase
|
||||
{ }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace xAiApi.Interfaces.Extractors
|
||||
{
|
||||
public interface IXVisionFileContentExtractor : IXFileContentExtractorBase
|
||||
{ }
|
||||
}
|
||||
+131
-14
@@ -1,9 +1,11 @@
|
||||
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;
|
||||
@@ -49,19 +51,6 @@ namespace xAiApi.Interfaces
|
||||
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>
|
||||
@@ -86,20 +75,146 @@ namespace xAiApi.Interfaces
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Data Provider Based Actions ...
|
||||
#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 Content Extraction ...
|
||||
/// <summary>
|
||||
/// Try to Extract Files Content ...
|
||||
/// </summary>
|
||||
/// <param name="files"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
Task<string> ExtractContent(
|
||||
IFormFileCollection files,
|
||||
string[] extractors = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Data Actions ...
|
||||
/// <summary>
|
||||
/// Ask a Question as Prompt and Retrieve Response ...
|
||||
/// </summary>
|
||||
@@ -109,6 +224,7 @@ namespace xAiApi.Interfaces
|
||||
/// <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(
|
||||
@@ -118,6 +234,7 @@ namespace xAiApi.Interfaces
|
||||
Guid conversationId,
|
||||
string connectionId = null,
|
||||
IFormFileCollection files = null,
|
||||
XUserClaimsInfoDto userInfo = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using xModels.Dtos;
|
||||
using System.Threading;
|
||||
using xAiModels.Models;
|
||||
using xAiModels.Models.Dtos;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace xAiApi.Interfaces
|
||||
@@ -37,17 +40,138 @@ namespace xAiApi.Interfaces
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Data Provided Actions ...
|
||||
#region Loaders ...
|
||||
/// <summary>
|
||||
/// Load AI Projects ...
|
||||
/// Retrieve AI Projects ...
|
||||
/// </summary>
|
||||
/// <param name="includeDefaults"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("LoadProjects")]
|
||||
[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 Content Extraction ...
|
||||
/// <summary>
|
||||
/// Extract Files Content ...
|
||||
/// </summary>
|
||||
/// <param name="files"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("ExtractContent")]
|
||||
[Consumes("multipart/form-data")]
|
||||
Task<ActionResult<string>> ExtractContent(
|
||||
[FromForm] IFormFileCollection files,
|
||||
[FromForm] string extractors = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Data Actions ...
|
||||
/// <summary>
|
||||
/// Ask Question and Get Response ...
|
||||
/// </summary>
|
||||
@@ -71,6 +195,20 @@ namespace xAiApi.Interfaces
|
||||
[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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Tesseract;
|
||||
|
||||
namespace xAiApi.Models
|
||||
{
|
||||
public class XAiOCRDescriptor
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides Trained Data Path ...
|
||||
/// </summary>
|
||||
public string DataPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Engine for Doing OCR ...
|
||||
/// </summary>
|
||||
public EngineMode EngineMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable OCR Fallback using Model ...
|
||||
/// </summary>
|
||||
public bool EnableOcrFallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default OCR Language ...
|
||||
/// </summary>
|
||||
public string DefaultLanguage { get; set; }
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,159 @@
|
||||
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;
|
||||
using DocumentFormat.OpenXml.EMMA;
|
||||
|
||||
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() : this(
|
||||
language: "fa",
|
||||
whisperModelPath: "Models/ggml-base.bin"
|
||||
)
|
||||
{ }
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using ExcelDataReader;
|
||||
using System.Threading;
|
||||
using xAiModels.Models;
|
||||
using System.Threading.Tasks;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using xAiModels.Models;
|
||||
using xCommons.Extensions;
|
||||
using xExceptions.Constants;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
|
||||
namespace xAiApi.Providers.Extractors
|
||||
{
|
||||
/// <summary>
|
||||
/// Composite extractor that delegates to appropriate extractor
|
||||
/// based on MIME type ...
|
||||
/// </summary>
|
||||
public class XFileContentExtractor : IXFileContentExtractor
|
||||
{
|
||||
//
|
||||
private readonly IList<IXFileContentExtractorBase> _extractors = [];
|
||||
|
||||
public XFileContentExtractor(
|
||||
IEnumerable<IXFileContentExtractorBase> availableExtractors
|
||||
)
|
||||
{
|
||||
//
|
||||
_extractors = [.. availableExtractors
|
||||
.Where(e => e.GetType() != typeof(XFileContentExtractor))];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this extractor supports the specified MIME type ...
|
||||
/// </summary>
|
||||
/// <param name="mimeType"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <returns></returns>
|
||||
public bool CanExtract(
|
||||
string mimeType,
|
||||
string[] extractors = null
|
||||
)
|
||||
{
|
||||
//
|
||||
return GetExtractors(extractors)
|
||||
.Any(e => e.CanExtract(mimeType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract text content from file stream ...
|
||||
/// </summary>
|
||||
/// <param name="fileStream"></param>
|
||||
/// <param name="mimeType"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> ExtractAsync(
|
||||
Stream fileStream,
|
||||
string mimeType,
|
||||
string[] extractors = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
//
|
||||
var extractor = GetExtractors(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="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<XFileExtractionResult> ExtractRichAsync(
|
||||
Stream fileStream,
|
||||
string fileName,
|
||||
string mimeType,
|
||||
string[] extractors = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
//
|
||||
var extractor = GetExtractors(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract Provided Extractors ...
|
||||
/// </summary>
|
||||
/// <param name="extractors"></param>
|
||||
/// <returns></returns>
|
||||
private IList<IXFileContentExtractorBase> GetExtractors(string[] extractors = null)
|
||||
{
|
||||
//
|
||||
var result = new List<IXFileContentExtractorBase>();
|
||||
|
||||
//
|
||||
if (extractors.IsNull() || !extractors.HasChild())
|
||||
{
|
||||
//
|
||||
result = [.. _extractors];
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
result = [.. _extractors
|
||||
.Where(e => extractors
|
||||
.Any(ex => e.GetType().FullName.Contains(ex)))];
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using xAiModels.Models;
|
||||
using System.Threading.Tasks;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
using xAiApi.Configurations;
|
||||
using xCommons.Extensions;
|
||||
using xExceptions.Constants;
|
||||
using Tesseract;
|
||||
|
||||
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>
|
||||
/// AI Api Configuration ...
|
||||
/// </summary>
|
||||
private readonly XAiApiConfiguration configuration;
|
||||
|
||||
public XImageFileContentExtractor(
|
||||
XAiApiConfiguration configuration
|
||||
)
|
||||
{
|
||||
//
|
||||
if (configuration.IsNullOrDefault() ||
|
||||
configuration.OCR.IsNullOrDefault() ||
|
||||
configuration.OCR.DataPath.IsNullOrEmpty() ||
|
||||
configuration.OCR.DefaultLanguage.IsNullOrEmpty())
|
||||
{
|
||||
XException.InvalidConfiguration.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/// <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 (configuration.OCR.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
|
||||
)
|
||||
{
|
||||
//
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
//
|
||||
// Validate Path ...
|
||||
if (!Directory.Exists(configuration.OCR.DataPath))
|
||||
{
|
||||
//
|
||||
throw new DirectoryNotFoundException(
|
||||
$"TessData directory not found at: {configuration.OCR.DataPath}. " +
|
||||
"Please download traineddata files from https://github.com/tesseract-ocr/tessdata"
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
using var engine = new TesseractEngine(
|
||||
datapath: configuration.OCR.DataPath,
|
||||
language: configuration.OCR.DefaultLanguage,
|
||||
engineMode: configuration.OCR.EngineMode
|
||||
);
|
||||
|
||||
//
|
||||
using var pix = Pix.LoadFromMemory(imageBytes);
|
||||
|
||||
//
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
//
|
||||
using var page = engine.Process(pix);
|
||||
|
||||
//
|
||||
var text = page.GetText();
|
||||
|
||||
//
|
||||
text = text?.Trim();
|
||||
|
||||
//
|
||||
return text ?? string.Empty;
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
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.Extractors
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
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
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using xAiApi.Constants;
|
||||
using xAiModels.Models;
|
||||
using xAiApi.Extensions;
|
||||
using xAiApi.Interfaces;
|
||||
using xCommons.Extensions;
|
||||
using xAiModels.Extensions;
|
||||
using xAiApi.Configurations;
|
||||
using xExceptions.Constants;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using System.Collections.Generic;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace xAiApi.Providers.Extractors
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts content from files using Vision Model ...
|
||||
/// </summary>
|
||||
public class XVisionFileContentExtractor : IXVisionFileContentExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported MIME Types ...
|
||||
/// </summary>
|
||||
private static readonly string[] SupportedMimeTypes =
|
||||
[
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/webp",
|
||||
"image/bmp",
|
||||
"application/pdf",
|
||||
];
|
||||
|
||||
//
|
||||
private readonly string prompt;
|
||||
private readonly XAiModelDescriptor descriptor;
|
||||
|
||||
public XVisionFileContentExtractor(
|
||||
XAiApiConfiguration configuration,
|
||||
string promptName = XAiApiConstants.XAiApiContentExtractionPromptName,
|
||||
string modelName = XAiApiConstants.XAiDefaultVisionModelName
|
||||
)
|
||||
{
|
||||
//
|
||||
// Retrieve Descriptoir ...
|
||||
if (modelName.IsNullOrEmpty())
|
||||
{
|
||||
XException.InvalidArgs.Throw();
|
||||
}
|
||||
descriptor = configuration.GetModel(modelName);
|
||||
|
||||
//
|
||||
// Retrieve Extraction Prompt ...
|
||||
if (promptName.IsNullOrEmpty())
|
||||
{
|
||||
XException.InvalidArgs.Throw();
|
||||
}
|
||||
prompt = configuration.GetPrompt(
|
||||
name: promptName,
|
||||
@params: null
|
||||
);
|
||||
|
||||
//
|
||||
// Validate Requirements ...
|
||||
if (prompt.IsNullOrEmpty() || descriptor.IsNullOrDefault())
|
||||
{
|
||||
XException.InvalidData.Throw();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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, "file", 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
|
||||
};
|
||||
|
||||
//
|
||||
try
|
||||
{
|
||||
//
|
||||
using var memoryStream = new MemoryStream();
|
||||
await fileStream.CopyToAsync(memoryStream, cancellationToken);
|
||||
var imageBytes = memoryStream.ToArray();
|
||||
var messages = new[]
|
||||
{
|
||||
//
|
||||
new ChatMessage(ChatRole.User, new[]
|
||||
{
|
||||
new DataContent(imageBytes, mimeType)
|
||||
})
|
||||
};
|
||||
|
||||
//
|
||||
var extractedContent = await RequestOCRAsync(
|
||||
messages: messages,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
if (!extractedContent.IsNullOrEmpty())
|
||||
{
|
||||
result.Text = extractedContent.Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
result.ErrorMessage = "there is not any Extracted Content ...";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.ErrorMessage = $"Error in Vision Extraction: {ex.Message} ...";
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
//
|
||||
#region OCR ...
|
||||
/// <summary>
|
||||
/// Request for Doing OCR on Given Data Contents ...
|
||||
/// </summary>
|
||||
/// <param name="messages"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async Task<string> RequestOCRAsync(
|
||||
IList<ChatMessage> messages,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
//
|
||||
// Validate ...
|
||||
if (!messages.HasChild())
|
||||
{
|
||||
XException.InvalidArgs.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
using var client = descriptor.GetClient();
|
||||
|
||||
//
|
||||
// Preparing Extraction Prompt Message ...
|
||||
var pMessage = new ChatMessage(
|
||||
ChatRole.System,
|
||||
prompt
|
||||
);
|
||||
|
||||
//
|
||||
messages = [pMessage, .. messages];
|
||||
|
||||
//
|
||||
var response = await client.GetResponseAsync(
|
||||
messages: messages,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
|
||||
//
|
||||
// Validate Response ...
|
||||
if (!response.IsValid())
|
||||
{
|
||||
//
|
||||
// Dispose Client ...
|
||||
client.Dispose();
|
||||
XException.ActionFailed.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve Response Text ...
|
||||
var result = response.Text;
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request for Doing OCR on Given Data Contents as Stream ...
|
||||
/// </summary>
|
||||
/// <param name="messages"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public virtual async IAsyncEnumerable<string> RequestOCRAsEnumerable(
|
||||
IList<ChatMessage> messages,
|
||||
[EnumeratorCancellation]
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
//
|
||||
// Validate ...
|
||||
if (!messages.HasChild())
|
||||
{
|
||||
XException.InvalidArgs.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
using var client = descriptor.GetClient();
|
||||
|
||||
//
|
||||
// Preparing Extraction Prompt Message ...
|
||||
var pMessage = new ChatMessage(
|
||||
ChatRole.System,
|
||||
prompt
|
||||
);
|
||||
|
||||
//
|
||||
messages = [pMessage, .. messages];
|
||||
|
||||
//
|
||||
var enumerable = client.GetStreamingResponseAsync(
|
||||
options: null,
|
||||
messages: messages
|
||||
);
|
||||
|
||||
//
|
||||
await foreach (var res in enumerable)
|
||||
{
|
||||
//
|
||||
// Cancellation Token ...
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
//
|
||||
yield return res.Text;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+551
-189
@@ -2,6 +2,7 @@ using System;
|
||||
using OpenAI;
|
||||
using OllamaSharp;
|
||||
using System.Linq;
|
||||
using xModels.Dtos;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using xAiApi.Constants;
|
||||
@@ -14,15 +15,19 @@ 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;
|
||||
|
||||
@@ -43,24 +48,30 @@ namespace xAiApi.Providers
|
||||
/// </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 ...
|
||||
@@ -96,7 +107,7 @@ namespace xAiApi.Providers
|
||||
}
|
||||
|
||||
//
|
||||
using var client = GetClient();
|
||||
using var client = Descriptor.GetClient();
|
||||
var history = GetHistory(prompt);
|
||||
var response = await client.GetResponseAsync(
|
||||
messages: history,
|
||||
@@ -141,7 +152,7 @@ namespace xAiApi.Providers
|
||||
}
|
||||
|
||||
//
|
||||
using var client = GetClient();
|
||||
using var client = Descriptor.GetClient();
|
||||
var history = GetHistory(prompt);
|
||||
var enumerable = client.GetStreamingResponseAsync(
|
||||
options: null,
|
||||
@@ -163,106 +174,6 @@ namespace xAiApi.Providers
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
@@ -346,17 +257,19 @@ namespace xAiApi.Providers
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Data Provider Based Actions ...
|
||||
#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(
|
||||
public virtual async Task<IEnumerable<XAiProjectDto>> LoadProjects(
|
||||
string ownerId,
|
||||
string connectionId = null,
|
||||
bool includeDefaults = false,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
@@ -377,42 +290,399 @@ namespace xAiApi.Providers
|
||||
);
|
||||
|
||||
//
|
||||
// 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);
|
||||
// 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 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
|
||||
);
|
||||
//
|
||||
// Reading Default Conversations Messages ...
|
||||
var defaultConversationMessages = await dataProvider.LoadMessages(
|
||||
cancellationToken: cancellationToken,
|
||||
conversationId: defaultConversationId
|
||||
);
|
||||
|
||||
//
|
||||
defaultConversation.Messages = [.. defaultConversationMessages];
|
||||
defaultProjectConversations = [defaultConversation, .. defaultProjectConversations];
|
||||
result = [defaultProject, .. result];
|
||||
//
|
||||
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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 virtual 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 Content Extraction ...
|
||||
/// <summary>
|
||||
/// Try to Extract Files Content ...
|
||||
/// </summary>
|
||||
/// <param name="files"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> ExtractContent(
|
||||
IFormFileCollection files,
|
||||
string[] extractors = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
//
|
||||
if (!files.HasChild())
|
||||
{
|
||||
XException.InvalidArgs.Throw();
|
||||
}
|
||||
|
||||
//
|
||||
var extractionResults = new List<XFileExtractionResult>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
//
|
||||
if (file.IsNull() || file.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
var mimeType = file.ContentType?.ToLowerInvariant() ?? string.Empty;
|
||||
if (!fileContentExtractor.CanExtract(mimeType, extractors))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
try
|
||||
{
|
||||
//
|
||||
using var stream = file.OpenReadStream();
|
||||
|
||||
//
|
||||
var fr = await fileContentExtractor.ExtractRichAsync(
|
||||
fileStream: stream,
|
||||
mimeType: mimeType,
|
||||
extractors: extractors,
|
||||
fileName: file.FileName,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
|
||||
//
|
||||
if (fr.IsValid())
|
||||
{
|
||||
extractionResults.Add(fr);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
var result = string.Empty;
|
||||
if (extractionResults.HasChild())
|
||||
{
|
||||
//
|
||||
extractionResults.ForEach(er =>
|
||||
{
|
||||
result += $"File {er.FileName} Content:{Environment.NewLine}{er.Text ?? er.AudioTranscript}{Environment.NewLine}----";
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
//
|
||||
#region Data Actions ...
|
||||
/// <summary>
|
||||
/// Ask a Question as Prompt and Retrieve Response ...
|
||||
/// </summary>
|
||||
@@ -422,15 +692,17 @@ namespace xAiApi.Providers
|
||||
/// <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(
|
||||
public virtual async Task<XAiMessageDto> AskAsync(
|
||||
string prompt,
|
||||
string ownerId,
|
||||
Guid projectId,
|
||||
Guid conversationId,
|
||||
string connectionId = null,
|
||||
IFormFileCollection files = null,
|
||||
XUserClaimsInfoDto userInfo = null,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
@@ -510,8 +782,26 @@ namespace xAiApi.Providers
|
||||
);
|
||||
|
||||
//
|
||||
// TODO: Parse Files ...
|
||||
// for Attached them into Prompt Message ...
|
||||
// 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 ...
|
||||
@@ -530,22 +820,101 @@ namespace xAiApi.Providers
|
||||
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
|
||||
);
|
||||
promptMessage = await dataProvider.Enrich(
|
||||
source: promptMessage,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
|
||||
//
|
||||
// Attach Uploaded Files ...
|
||||
// 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();
|
||||
.ToChatMessages(fileContents);
|
||||
|
||||
//
|
||||
// Ask Questions From LLM ...
|
||||
@@ -561,9 +930,10 @@ namespace xAiApi.Providers
|
||||
{
|
||||
Content = answer,
|
||||
OwnerId = ownerId,
|
||||
Role = XAiChatRole.System,
|
||||
CreatedOn = DateTime.UtcNow,
|
||||
ConversationId = conversationId
|
||||
Role = XAiChatRole.Assistant,
|
||||
ConversationId = conversationId,
|
||||
ConversationTitle = conversation.Title
|
||||
};
|
||||
result = await dataProvider.AddMessage(
|
||||
item: result,
|
||||
@@ -571,10 +941,6 @@ namespace xAiApi.Providers
|
||||
conversationId: conversationId,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
result = await dataProvider.Enrich(
|
||||
source: result,
|
||||
cancellationToken: cancellationToken
|
||||
);
|
||||
|
||||
//
|
||||
return result;
|
||||
@@ -591,7 +957,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="files"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async IAsyncEnumerable<XAiResonseMessage> AskAsEnumerable(
|
||||
public virtual async IAsyncEnumerable<XAiResonseMessage> AskAsEnumerable(
|
||||
string prompt,
|
||||
string ownerId,
|
||||
Guid projectId,
|
||||
@@ -718,7 +1084,7 @@ namespace xAiApi.Providers
|
||||
|
||||
//
|
||||
// Ask Questions From LLM ...
|
||||
using var client = GetClient();
|
||||
using var client = Descriptor.GetClient();
|
||||
var enumerable = client.GetStreamingResponseAsync(
|
||||
options: null,
|
||||
messages: history
|
||||
@@ -799,49 +1165,45 @@ namespace xAiApi.Providers
|
||||
//
|
||||
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())
|
||||
{
|
||||
//
|
||||
// if Messages Has Child, this means
|
||||
// Default Messages Handled before ...
|
||||
result = [.. messages.Select(m => m.ToChatMessages())];
|
||||
}
|
||||
else
|
||||
{
|
||||
//
|
||||
// If Message Has not any Childs, this means
|
||||
// First Asking Questions ...
|
||||
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
// 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())];
|
||||
}
|
||||
|
||||
//
|
||||
@@ -869,7 +1231,7 @@ namespace xAiApi.Providers
|
||||
|
||||
//
|
||||
history.Add(prompt);
|
||||
using var client = GetClient();
|
||||
using var client = Descriptor.GetClient();
|
||||
var response = await client.GetResponseAsync(
|
||||
messages: history,
|
||||
cancellationToken: cancellationToken
|
||||
|
||||
@@ -9,6 +9,7 @@ using xAiApi.Extensions;
|
||||
using xAiApi.Interfaces;
|
||||
using xAiService.Helpers;
|
||||
using System.ClientModel;
|
||||
using xCommons.Providers;
|
||||
using xCommons.Extensions;
|
||||
using xAiModels.Constants;
|
||||
using xAiModels.Extensions;
|
||||
@@ -16,9 +17,11 @@ 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;
|
||||
|
||||
namespace xAiApi.Providers
|
||||
@@ -28,15 +31,27 @@ namespace xAiApi.Providers
|
||||
/// </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(dataProvider, logger, configuration, null)
|
||||
) : base(
|
||||
fileProvider,
|
||||
dataProvider,
|
||||
logger,
|
||||
configuration,
|
||||
validationProvider,
|
||||
fileContentExtractor,
|
||||
null
|
||||
)
|
||||
{
|
||||
//
|
||||
// Prepare Embedding Model Descriptor ...
|
||||
@@ -46,6 +61,8 @@ namespace xAiApi.Providers
|
||||
XException.InvalidConfiguration.Throw();
|
||||
}
|
||||
|
||||
this.fileProvider = fileProvider;
|
||||
|
||||
//
|
||||
this.logger = logger;
|
||||
}
|
||||
@@ -182,7 +199,7 @@ namespace xAiApi.Providers
|
||||
new OpenAIClientOptions
|
||||
{
|
||||
Endpoint = new Uri(descriptor.Url),
|
||||
Transport = new HttpClientPipelineTransport(GetHttpClient(descriptor.Url))
|
||||
Transport = new HttpClientPipelineTransport(descriptor.Url.GetHttpClient())
|
||||
}
|
||||
);
|
||||
result = openAiClient
|
||||
|
||||
@@ -3,22 +3,28 @@ 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
|
||||
XValidationProvider validationProvider,
|
||||
IXFileContentExtractor fileContentExtractor
|
||||
) : base(
|
||||
fileProvider,
|
||||
dataProvider,
|
||||
logger,
|
||||
configuration,
|
||||
validationProvider,
|
||||
fileContentExtractor,
|
||||
XAiApiConstants.XAiDefaultModelName
|
||||
)
|
||||
{ }
|
||||
|
||||
@@ -2,20 +2,29 @@ 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(
|
||||
IXAiDataProvider dataProvider,
|
||||
XAiApiConfiguration configuration,
|
||||
ILogger<XDefaultEmbeddingService> logger
|
||||
IXFileProvider fileProvider,
|
||||
IXAiDataProvider dataProvider,
|
||||
XAiApiConfiguration configuration,
|
||||
XValidationProvider validationProvider,
|
||||
ILogger<XDefaultEmbeddingService> logger,
|
||||
IXFileContentExtractor fileContentExtractor
|
||||
) : base(
|
||||
dataProvider,
|
||||
configuration,
|
||||
fileProvider,
|
||||
dataProvider,
|
||||
configuration,
|
||||
validationProvider,
|
||||
logger,
|
||||
fileContentExtractor,
|
||||
XAiApiConstants.XAiEmbeddingModelName
|
||||
)
|
||||
{ }
|
||||
|
||||
@@ -4,24 +4,30 @@ 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
|
||||
)
|
||||
{
|
||||
|
||||
+15
@@ -23,9 +23,11 @@ using xCommons.Extensions;
|
||||
using xPushService.Helpers;
|
||||
using xCommons.Configurations;
|
||||
using System.Collections.Generic;
|
||||
using xAiApi.Providers.Extractors;
|
||||
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;
|
||||
@@ -185,6 +187,19 @@ namespace xAiApi
|
||||
// Register XAiApi Configuration ...
|
||||
services.AddXAiApiConfiguration(Configuration);
|
||||
|
||||
//
|
||||
// Register File Content Extractors ...
|
||||
services.AddSingleton<IXFileContentExtractorBase, XPdfFileContentExtractor>();
|
||||
services.AddSingleton<IXFileContentExtractorBase, XDocxFileContentExtractor>();
|
||||
services.AddSingleton<IXFileContentExtractorBase, XImageFileContentExtractor>();
|
||||
services.AddSingleton<IXFileContentExtractorBase, XAudioFileContentExtractor>();
|
||||
services.AddSingleton<IXFileContentExtractorBase, XExcelFileContentExtractor>();
|
||||
services.AddSingleton<IXFileContentExtractorBase, XVisionFileContentExtractor>();
|
||||
services.AddSingleton<IXFileContentExtractorBase, XPlainTextFileContentExtractor>();
|
||||
|
||||
//
|
||||
services.AddSingleton<IXFileContentExtractor, XFileContentExtractor>();
|
||||
|
||||
//
|
||||
// Register Default Ai Service ...
|
||||
services.AddScoped<IXDefaultAiService, XDefaultAiService>();
|
||||
|
||||
@@ -66,18 +66,30 @@
|
||||
"Description": "",
|
||||
"Params": []
|
||||
},
|
||||
{
|
||||
"Name": "ContentExtraction",
|
||||
"Template": "You are an Expert Powerful OCR Engine. your job isReading all Contents in Given Document and Retrieve them as Raw Text by following these rules: 1. Converst all tables to Markdown format. 2. extract numbers, date and times and names exactly as is. 3. do not add any extra descriptions, introduction or summarization from yourself and just return extracted Content.",
|
||||
"Description": "",
|
||||
"Params": []
|
||||
},
|
||||
{
|
||||
"Name": "Embedding",
|
||||
"Template": "your name is xSaherElmAi, you are a professional ai assistant which develped by SaherElm IT Center specified for Embedding Documents",
|
||||
"Description": "",
|
||||
"Params": []
|
||||
}
|
||||
]
|
||||
],
|
||||
"OCR": {
|
||||
"DataPath": "Models",
|
||||
"EngineMode": "LstmOnly",
|
||||
"EnableOcrFallback": true,
|
||||
"DefaultLanguage": "fas+eng"
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Default": "Information",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
|
||||
+21
-2
@@ -24,10 +24,16 @@
|
||||
|
||||
<!-- Icon Handling -->
|
||||
<ItemGroup>
|
||||
<None Include="../../Resources/Images/favicon.png" Link="icon.png" Pack="true"
|
||||
PackagePath="\icon.png" />
|
||||
<None Include="../../Resources/Images/favicon.png" Link="icon.png" Pack="true" PackagePath="\icon.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Native Libraries -->
|
||||
<!-- <ItemGroup>
|
||||
<None Include="runtimes/linux-x64/native/libpdfium.so">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup> -->
|
||||
|
||||
<!-- Local Modules -->
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
@@ -47,9 +53,22 @@
|
||||
|
||||
<!-- Dependencies -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
|
||||
<PackageReference Include="ExcelDataReader" Version="3.9.0" />
|
||||
<PackageReference Include="ExcelDataReader.DataSet" Version="3.9.0" />
|
||||
<PackageReference Include="NAudio" Version="2.4.0" />
|
||||
<PackageReference Include="OllamaSharp" Version="5.4.25" />
|
||||
<PackageReference Include="PdfiumViewer" Version="2.13.0" />
|
||||
<PackageReference Include="PdfiumViewer.Native.x86.no_v8-no_xfa" Version="2018.4.8.256" />
|
||||
<PackageReference Include="PdfiumViewer.Native.x86.v8-xfa" Version="2018.4.8.256" />
|
||||
<PackageReference Include="PdfiumViewer.Native.x86_64.no_v8-no_xfa" Version="2018.4.8.256" />
|
||||
<PackageReference Include="PdfiumViewer.Native.x86_64.v8-xfa" Version="2018.4.8.256" />
|
||||
<PackageReference Include="Tesseract" Version="5.2.0" />
|
||||
<PackageReference Include="UglyToad.PdfPig" Version="1.7.0-custom-5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.11" />
|
||||
<PackageReference Include="IdentityModel.AspNetCore.OAuth2Introspection" Version="5.0.0" />
|
||||
<PackageReference Include="Whisper.net" Version="1.9.1" />
|
||||
<PackageReference Include="Whisper.net.Runtime" Version="1.9.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- For XML Documentation Support -->
|
||||
|
||||
Reference in New Issue
Block a user