last ...
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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;
|
||||
@@ -75,5 +76,39 @@ namespace xAiApi.Providers.Extractors
|
||||
//
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using ExcelDataReader;
|
||||
using System.Threading;
|
||||
using xAiModels.Models;
|
||||
using System.Threading.Tasks;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
|
||||
@@ -89,5 +90,39 @@ namespace xAiApi.Providers.Extractors
|
||||
//
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Reflection;
|
||||
using xAiModels.Models;
|
||||
using xCommons.Extensions;
|
||||
using xExceptions.Constants;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
|
||||
namespace xAiApi.Providers
|
||||
namespace xAiApi.Providers.Extractors
|
||||
{
|
||||
/// <summary>
|
||||
/// Composite extractor that delegates to appropriate extractor
|
||||
@@ -18,67 +17,50 @@ namespace xAiApi.Providers
|
||||
public class XFileContentExtractor : IXFileContentExtractor
|
||||
{
|
||||
//
|
||||
private readonly IList<IXFileContentExtractor> extractors = [];
|
||||
private readonly IList<IXFileContentExtractorBase> _extractors = [];
|
||||
|
||||
public XFileContentExtractor()
|
||||
public XFileContentExtractor(
|
||||
IEnumerable<IXFileContentExtractorBase> availableExtractors
|
||||
)
|
||||
{
|
||||
//
|
||||
var path = Path.Combine(AppContext.BaseDirectory);
|
||||
var files = Directory.GetFiles(path, "*.dll");
|
||||
foreach (var dll in files)
|
||||
{
|
||||
//
|
||||
// Retrieve DLL Assembly ...
|
||||
var assembly = Assembly.LoadFrom(dll);
|
||||
|
||||
//
|
||||
// Extract Registerar Types ...
|
||||
var extractorTypes = assembly
|
||||
.GetExportedTypes()
|
||||
.Where(t =>
|
||||
!t.IsAbstract &&
|
||||
typeof(IXFileContentExtractor).IsAssignableFrom(t));
|
||||
foreach (var extractorType in extractorTypes)
|
||||
{
|
||||
//
|
||||
// Ignore XFileContentExtractor ...
|
||||
if (extractorType.FullName.Contains(nameof(XFileContentExtractor)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//
|
||||
// Create Instance of Service ...
|
||||
var instance = (IXFileContentExtractor)Activator.CreateInstance(extractorType);
|
||||
if (!instance.IsNull())
|
||||
{
|
||||
//
|
||||
// Add Registerar Instance to Collection ...
|
||||
extractors.Add(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
_extractors = [.. availableExtractors
|
||||
.Where(e => e.GetType() != typeof(XFileContentExtractor))];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this extractor supports the specified MIME type ...
|
||||
/// </summary>
|
||||
public bool CanExtract(string mimeType)
|
||||
/// <param name="mimeType"></param>
|
||||
/// <param name="extractors"></param>
|
||||
/// <returns></returns>
|
||||
public bool CanExtract(
|
||||
string mimeType,
|
||||
string[] extractors = null
|
||||
)
|
||||
{
|
||||
return extractors.Any(e => e.CanExtract(mimeType));
|
||||
//
|
||||
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 = extractors
|
||||
var extractor = GetExtractors(extractors)
|
||||
.FirstOrDefault(e => e.CanExtract(mimeType));
|
||||
if (extractor.IsNull())
|
||||
{
|
||||
@@ -99,5 +81,74 @@ namespace xAiApi.Providers
|
||||
//
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,9 @@ namespace xAiApi.Providers.Extractors
|
||||
/// </summary>
|
||||
private readonly bool _enableOcrFallback;
|
||||
|
||||
public XImageFileContentExtractor() : this(enableOcrFallback: true)
|
||||
{ }
|
||||
|
||||
public XImageFileContentExtractor(bool enableOcrFallback = true)
|
||||
{
|
||||
_enableOcrFallback = enableOcrFallback;
|
||||
@@ -132,9 +135,7 @@ namespace xAiApi.Providers.Extractors
|
||||
)
|
||||
{
|
||||
//
|
||||
// TODO:
|
||||
// پیادهسازی با Tesseract یا هر کتابخانه OCR دیگر
|
||||
// در حال حاضر یک placeholder است
|
||||
// TODO: Complete this ...
|
||||
return await Task.FromResult(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UglyToad.PdfPig;
|
||||
using PdfiumViewer;
|
||||
using System.Threading;
|
||||
using xAiModels.Models;
|
||||
using System.Threading.Tasks;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
|
||||
namespace xAiApi.Providers
|
||||
namespace xAiApi.Providers.Extractors
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts content from PDF files using PdfPig ...
|
||||
@@ -22,6 +23,26 @@ namespace xAiApi.Providers
|
||||
"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>
|
||||
@@ -41,21 +62,100 @@ namespace xAiApi.Providers
|
||||
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 = PdfDocument.Open(fileStream);
|
||||
using var document = UglyToad.PdfPig.PdfDocument.Open(pdfStream);
|
||||
foreach (var page in document.GetPages())
|
||||
{
|
||||
//
|
||||
var text = page.Text;
|
||||
|
||||
//
|
||||
sb.AppendLine(text);
|
||||
sb.AppendLine();
|
||||
var text = page.Text?.Trim();
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
//
|
||||
sb.AppendLine(text);
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -65,5 +165,129 @@ namespace xAiApi.Providers
|
||||
//
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,11 @@ 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
|
||||
namespace xAiApi.Providers.Extractors
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts content from plain text files (txt, md, csv, json, xml) ...
|
||||
@@ -50,5 +51,39 @@ namespace xAiApi.Providers
|
||||
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
|
||||
}
|
||||
}
|
||||
+155
-126
@@ -107,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,
|
||||
@@ -152,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,
|
||||
@@ -174,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>
|
||||
@@ -366,7 +266,7 @@ namespace xAiApi.Providers
|
||||
/// <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,
|
||||
@@ -439,7 +339,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="connectionId"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<XAiConversationDto>> LoadConversations(
|
||||
public virtual async Task<IEnumerable<XAiConversationDto>> LoadConversations(
|
||||
Guid projectId,
|
||||
string connectionId = null,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -470,7 +370,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="conversationId"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<XAiMessageDto>> LoadMessages(
|
||||
public virtual async Task<IEnumerable<XAiMessageDto>> LoadMessages(
|
||||
Guid conversationId,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
@@ -502,7 +402,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="query"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<XQueryResult<XAiProjectDto>> QueryProjects(
|
||||
public virtual async Task<XQueryResult<XAiProjectDto>> QueryProjects(
|
||||
string ownerId,
|
||||
XQuery query,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -534,7 +434,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="query"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<XQueryResult<XAiConversationDto>> QueryConversations(
|
||||
public virtual async Task<XQueryResult<XAiConversationDto>> QueryConversations(
|
||||
string ownerId,
|
||||
Guid projectId,
|
||||
XQuery query,
|
||||
@@ -589,7 +489,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="query"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<XQueryResult<XAiMessageDto>> QueryMessages(
|
||||
public virtual async Task<XQueryResult<XAiMessageDto>> QueryMessages(
|
||||
string ownerId,
|
||||
Guid conversationId,
|
||||
XQuery query,
|
||||
@@ -646,7 +546,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="connectionId"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<XAiProjectDto> CreateProject(
|
||||
public virtual async Task<XAiProjectDto> CreateProject(
|
||||
string ownerId,
|
||||
XAiProjectDto item,
|
||||
string connectionId = null,
|
||||
@@ -674,7 +574,7 @@ namespace xAiApi.Providers
|
||||
/// <param name="connectionId"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<XAiConversationDto> CreateConversation(
|
||||
public virtual async Task<XAiConversationDto> CreateConversation(
|
||||
string ownerId,
|
||||
Guid projectId,
|
||||
XAiConversationDto item,
|
||||
@@ -696,6 +596,91 @@ namespace xAiApi.Providers
|
||||
}
|
||||
#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>
|
||||
@@ -710,7 +695,7 @@ namespace xAiApi.Providers
|
||||
/// <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,
|
||||
@@ -861,25 +846,69 @@ namespace xAiApi.Providers
|
||||
|
||||
//
|
||||
// Since Prompt Message Has Id now, we can Attach Uploaded Files to it ...
|
||||
IList<AIContent> fileContents = null;
|
||||
IList<AIContent> fileContents = [];
|
||||
if (uploadedFiles.HasChild())
|
||||
{
|
||||
//
|
||||
foreach (var uf in uploadedFiles)
|
||||
{
|
||||
//
|
||||
await fileProvider.AddReference(
|
||||
id: uf.Id,
|
||||
userInfo: userInfo,
|
||||
connectionId: connectionId,
|
||||
forProvidedId: promptMessage.Id,
|
||||
cancellationToken: cancellationToken,
|
||||
providedFor: XAiServiceConstants.XAiMessageFilesIdentifier
|
||||
);
|
||||
}
|
||||
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
|
||||
);
|
||||
|
||||
//
|
||||
// TODO: Extract File Contents ...
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -928,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,
|
||||
@@ -1055,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
|
||||
@@ -1202,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;
|
||||
@@ -22,7 +23,6 @@ using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using xAiApi.Interfaces.Extractors;
|
||||
using System.ClientModel.Primitives;
|
||||
using xCommons.Providers;
|
||||
|
||||
namespace xAiApi.Providers
|
||||
{
|
||||
@@ -44,12 +44,12 @@ namespace xAiApi.Providers
|
||||
IXFileContentExtractor fileContentExtractor,
|
||||
string embeddingModelName = XAiApiConstants.XAiEmbeddingModelName
|
||||
) : base(
|
||||
fileProvider,
|
||||
dataProvider,
|
||||
logger,
|
||||
configuration,
|
||||
validationProvider,
|
||||
fileContentExtractor,
|
||||
fileProvider,
|
||||
dataProvider,
|
||||
logger,
|
||||
configuration,
|
||||
validationProvider,
|
||||
fileContentExtractor,
|
||||
null
|
||||
)
|
||||
{
|
||||
@@ -199,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
|
||||
|
||||
Reference in New Issue
Block a user