+
+
+
+
+
+
🔍 گام ۱: تحلیل وضعیت فعلی و TODO های موجود
+
+ با بررسی دقیق کد پروژه، مشخص شد که زیرساخت اولیه برای این قابلیت از قبل طراحی شده
+ و فقط نیاز به تکمیل دارد. در نقاط مختلف کد، TODO های مشخصی وجود دارد:
+
+
+
+
+
📍 TODO در Controller
+
+ در XAiServiceControllerBase.cs در متدهای Ask و AskStream:
+
+
// TODO: Reading Fiels Form Collection
+// and Attach it ...
+var result = await aiService.AskAsync(
+ files: null, // ❌ null ارسال میشود
+ ...
+);
+
+
+
📍 TODO در Service
+
+ در XAIServiceBase.cs در متدهای AskAsync و AskAsEnumerable:
+
+
// TODO: Parse Files ...
+// for Attached them into Prompt
+// Message ...
+
+
+
📍 TODO در Extension
+
+ در XAiModelsExtensions.cs در متد ToChatMessages:
+
+
// TODO: Handle Files Attached here ...
+ChatMessage result = null;
+
+
+
📍 Entity آماده
+
+ Entity XAiDocument از قبل طراحی شده و شامل فیلدهای Vector, ContentHash, EmbeddingModel است:
+
+
public class XAiDocument : XBaseGuidIDEntity
+{
+ public string Content { get; set; }
+ public string ContentHash { get; set; }
+ public string EmbeddingModel { get; set; }
+ public int Dimensions { get; set; }
+ public byte[] Vector { get; set; }
+}
+
+
+
+
+ 💡 نتیجهگیری: زیرساخت طراحی شده است، فقط نیاز به پیادهسازی و اتصال اجزا داریم.
+ این یک نشانه عالی از طراحی آیندهنگرانه معماری شماست! 🎯
+
+
+
+
+
+
🎯 گام ۲: انتخاب استراتژی مناسب
+
+ برای افزودن قابلیت فایل به پیامها، سه استراتژی اصلی وجود دارد. با توجه به معماری پروژه و Entity آماده XAiDocument،
+ استراتژی ترکیبی پیشنهاد میشود:
+
+
+
+
+ | استراتژی |
+ توضیح |
+ کاربرد |
+ پیچیدگی |
+
+
+ | Level 1 Inline Context |
+ محتوای فایل مستقیماً به prompt اضافه شود |
+ فایلهای متنی کوچک (TXT, MD, CSV) |
+ ⭐ ساده |
+
+
+ | Level 2 Multi-modal |
+ تصاویر به صورت DataContent ارسال شوند |
+ فایلهای تصویری (PNG, JPG) |
+ ⭐⭐ متوسط |
+
+
+ | Level 3 RAG |
+ فایل Embed شده و بخشهای مرتبط جستجو شوند |
+ فایلهای بزرگ (PDF, DOCX) |
+ ⭐⭐⭐ پیچیده |
+
+
+
+
+ ✅ پیشنهاد: ابتدا Level 1 و Level 2 را پیادهسازی میکنیم که بیشترین کاربرد را دارند.
+ Level 3 (RAG) در آینده با استفاده از Entity XAiDocument و سرویس Embedding موجود قابل افزودن است.
+
+
+
+
+
+
🏗️ گام ۳: معماری پیشنهادی
+
+
۳.۱ اجزای جدید مورد نیاز:
+
+
+
📄 XAiFileAttachment Entity
+
+ - رابطه بین Message و File
+ - ذخیره Metadata فایل
+ - ContentHash برای Deduplication
+
+
+
+
🔧 IFileContentExtractor
+
+ - استخراج متن از PDF
+ - استخراج متن از DOCX
+ - استخراج متن از TXT/MD
+ - استخراج متن از CSV/Excel
+
+
+
+
🎨 IFileToChatContentConverter
+
+ - تبدیل فایل به ChatContent
+ - پشتیبانی از TextContent
+ - پشتیبانی از DataContent (Image)
+
+
+
+
📦 XAiFileAttachmentService
+
+ - مدیریت آپلود فایل
+ - ارتباط با xFileService
+ - ذخیره Attachments
+
+
+
+
+
۳.۲ جریان کلی پردازش:
+
+ 📤 Upload Files
+ →
+ 💾 Save to Storage
+ →
+ 🔍 Extract Content
+ →
+ 🎨 Convert to ChatContent
+ →
+ 📎 Attach to Message
+ →
+ 🤖 Send to LLM
+
+
+
+
+
+
📝 گام ۴: افزودن Entity برای رابطه فایل و پیام
+
+
+ NEW ENTITY
+ xAiModels/Models/Entities/XAiFileAttachment.cs
+
+
+
using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using xModels.Base;
+
+namespace xAiModels.Models.Entities
+{
+ /// <summary>
+ /// Represents a file attached to an AI Message ...
+ /// </summary>
+ public class XAiFileAttachment : XBaseGuidIDEntity
+ {
+ /// <summary>
+ /// User Identifier ...
+ /// </summary>
+ [Required]
+ [StringLength(255)]
+ public string OwnerId { get; set; }
+
+ /// <summary>
+ /// Related Message Id ...
+ /// </summary>
+ [Required]
+ public Guid MessageId { get; set; }
+
+ /// <summary>
+ /// Related File Id (from xFileService) ...
+ /// </summary>
+ [Required]
+ public Guid FileId { get; set; }
+
+ /// <summary>
+ /// Original File Name ...
+ /// </summary>
+ [Required]
+ [StringLength(500)]
+ public string FileName { get; set; }
+
+ /// <summary>
+ /// File MIME Type ...
+ /// </summary>
+ [Required]
+ [StringLength(255)]
+ public string MimeType { get; set; }
+
+ /// <summary>
+ /// File Size in Bytes ...
+ /// </summary>
+ public long FileSize { get; set; }
+
+ /// <summary>
+ /// Extracted Text Content (for text-based files) ...
+ /// </summary>
+ public string ExtractedContent { get; set; }
+
+ /// <summary>
+ /// Hash of Extracted Content (for deduplication) ...
+ /// </summary>
+ [StringLength(255)]
+ public string ContentHash { get; set; }
+
+ /// <summary>
+ /// Processing Status ...
+ /// </summary>
+ public XAiFileProcessingStatus Status { get; set; }
+ = XAiFileProcessingStatus.Pending;
+
+ /// <summary>
+ /// Error Message (if processing failed) ...
+ /// </summary>
+ public string ErrorMessage { get; set; }
+
+ /// <summary>
+ /// Sequence Order in Message ...
+ /// </summary>
+ public int Order { get; set; }
+
+ /// <summary>
+ /// Created Time ...
+ /// </summary>
+ public DateTime CreatedOn { get; set; }
+
+ /// <summary>
+ /// Updated Time ...
+ /// </summary>
+ public DateTime UpdatedAt { get; set; }
+ }
+
+ /// <summary>
+ /// File Processing Status ...
+ /// </summary>
+ public enum XAiFileProcessingStatus
+ {
+ Pending = 0,
+ Processing = 1,
+ Completed = 2,
+ Failed = 3
+ }
+}
+
+
+ NEW DTO
+ xAiModels/Models/Dtos/XAiFileAttachmentDto.cs
+
+
+
using System;
+using xModels.Base;
+
+namespace xAiModels.Models.Dtos
+{
+ public class XAiFileAttachmentDto : XBaseGuidIDEntityDto
+ {
+ public string OwnerId { get; set; }
+ public Guid MessageId { get; set; }
+ public Guid FileId { get; set; }
+ public string FileName { get; set; }
+ public string MimeType { get; set; }
+ public long FileSize { get; set; }
+ public string ExtractedContent { get; set; }
+ public string ContentHash { get; set; }
+ public XAiFileProcessingStatus Status { get; set; }
+ public string ErrorMessage { get; set; }
+ public int Order { get; set; }
+ public DateTime CreatedOn { get; set; }
+ public DateTime UpdatedAt { get; set; }
+ }
+}
+
+
+ 💡 نکته طراحی: با ذخیره ExtractedContent در دیتابیس، از پردازش مجدد فایل در هر پرسش جلوگیری میکنیم
+ و performance بهینه میشود.
+
+
+
+
+
+
🔧 گام ۵: ایجاد File Content Extractor
+
+
+ NEW
+ xAiService/Interfaces/IFileContentExtractor.cs
+
+
+
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace xAiService.Interfaces
+{
+ /// <summary>
+ /// Extracts text content from various file types ...
+ /// </summary>
+ public interface IFileContentExtractor
+ {
+ /// <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
+ );
+ }
+}
+
+
+ NEW
+ xAiService/Providers/PlainTextContentExtractor.cs
+
+
+
using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using xAiService.Interfaces;
+
+namespace xAiService.Providers
+{
+ /// <summary>
+ /// Extracts content from plain text files (txt, md, csv, json, xml) ...
+ /// </summary>
+ public class PlainTextContentExtractor : IFileContentExtractor
+ {
+ private static readonly string[] SupportedMimeTypes = new[]
+ {
+ "text/plain",
+ "text/markdown",
+ "text/csv",
+ "text/html",
+ "text/xml",
+ "application/json",
+ "application/xml"
+ };
+
+ public bool CanExtract(string mimeType)
+ {
+ return SupportedMimeTypes.Contains(
+ mimeType?.ToLowerInvariant() ?? string.Empty
+ );
+ }
+
+ public async Task<string> ExtractAsync(
+ Stream fileStream,
+ string mimeType,
+ CancellationToken cancellationToken = default
+ )
+ {
+ using var reader = new StreamReader(fileStream);
+ return await reader.ReadToEndAsync();
+ }
+ }
+}
+
+
+ NEW
+ xAiService/Providers/PdfContentExtractor.cs
+
+
+
using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using xAiService.Interfaces;
+// Install-Package PdfPig
+using UglyToad.PdfPig;
+using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor;
+
+namespace xAiService.Providers
+{
+ /// <summary>
+ /// Extracts content from PDF files using PdfPig ...
+ /// </summary>
+ public class PdfContentExtractor : IFileContentExtractor
+ {
+ public bool CanExtract(string mimeType)
+ {
+ return mimeType?.ToLowerInvariant() == "application/pdf";
+ }
+
+ public async Task<string> ExtractAsync(
+ Stream fileStream,
+ string mimeType,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var result = await Task.Run(() =>
+ {
+ var sb = new StringBuilder();
+ using var document = PdfDocument.Open(fileStream);
+
+ foreach (var page in document.GetPages())
+ {
+ var text = ContentOrderTextExtractor
+ .GetText(page);
+ sb.AppendLine(text);
+ sb.AppendLine();
+ }
+
+ return sb.ToString();
+ }, cancellationToken);
+
+ return result;
+ }
+ }
+}
+
+
+ NEW
+ xAiService/Providers/CompositeFileContentExtractor.cs
+
+
+
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using xAiService.Interfaces;
+using xExceptions.Constants;
+
+namespace xAiService.Providers
+{
+ /// <summary>
+ /// Composite extractor that delegates to appropriate extractor
+ /// based on MIME type ...
+ /// </summary>
+ public class CompositeFileContentExtractor : IFileContentExtractor
+ {
+ private readonly IEnumerable<IFileContentExtractor> extractors;
+
+ public CompositeFileContentExtractor(
+ IEnumerable<IFileContentExtractor> extractors
+ )
+ {
+ this.extractors = extractors;
+ }
+
+ public bool CanExtract(string mimeType)
+ {
+ return extractors.Any(e => e.CanExtract(mimeType));
+ }
+
+ public async Task<string> ExtractAsync(
+ Stream fileStream,
+ string mimeType,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var extractor = extractors
+ .FirstOrDefault(e => e.CanExtract(mimeType));
+
+ if (extractor == null)
+ {
+ XException.InvalidData.Throw(
+ $"Unsupported file type: {mimeType}"
+ );
+ }
+
+ return await extractor.ExtractAsync(
+ fileStream,
+ mimeType,
+ cancellationToken
+ );
+ }
+ }
+}
+
+
+
⚠️ پکیجهای NuGet مورد نیاز:
+
+ UglyToad.PdfPig - برای استخراج متن از PDF
+ DocumentFormat.OpenXml - برای استخراج متن از DOCX (آینده)
+ ExcelDataReader - برای استخراج متن از Excel (آینده)
+
+
+
+
+
+
+
🎮 گام ۶: اصلاح Controller برای دریافت فایل
+
+
+ MODIFY
+ xAiApi/Controllers/XAiServiceControllerBase.cs
+
+
+
تغییرات در متد Ask:
+
+
// ❌ کد قبلی:
+[HttpPost("Ask")]
+public async Task<ActionResult<string>> Ask(
+ [FromBody] XAiResponseRequest request,
+ CancellationToken cancellationToken = default
+)
+{
+ // ...
+ // TODO: Reading Fiels Form Collection and Attach it ...
+ var result = await aiService.AskAsync(
+ files: null, // ❌ null
+ prompt: request.Prompt,
+ // ...
+ );
+}
+
+// ✅ کد جدید:
+[HttpPost("Ask")]
+[Consumes("multipart/form-data")]
+public async Task<ActionResult<string>> Ask(
+ [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, // ✅ فایلها ارسال میشوند
+ prompt: request.Prompt,
+ ownerId: userInfo.UserId,
+ connectionId: connectionId,
+ projectId: request.ProjectId,
+ cancellationToken: cancellationToken,
+ conversationId: request.ConversationId
+ );
+
+ return Ok(result);
+ }
+ catch (Exception ex)
+ {
+ return GetExceptionActionResult(ex);
+ }
+}
+
+
تغییر مشابه برای متد AskStream:
+
+
[HttpPost("AskStream")]
+[Consumes("multipart/form-data")]
+public async Task AskStream(
+ [FromForm] XAiResponseRequest request,
+ [FromForm] IFormFileCollection files,
+ CancellationToken cancellationToken = default
+)
+{
+ // ...
+ var enumerable = aiService.AskAsEnumerable(
+ files: files, // ✅ فایلها ارسال میشوند
+ prompt: request.Prompt,
+ ownerId: userInfo.UserId,
+ connectionId: connectionId,
+ projectId: request.ProjectId,
+ cancellationToken: cancellationToken,
+ conversationId: request.ConversationId
+ );
+ // ...
+}
+
+
+ 💡 نکته مهم: با تغییر از [FromBody] به [FromForm]،
+ امکان ارسال همزمان فایل و JSON فراهم میشود. توجه داشته باشید که XAiResponseRequest
+ باید به صورت multipart/form-data ارسال شود.
+
+
+
+
⚠️ سازگاری با کلاینتهای موجود: اگر میخواهید کلاینتهای فعلی که JSON ارسال میکنند
+ همچنان کار کنند، میتوانید
دو endpoint مجزا ایجاد کنید:
+
+ POST /Ask - برای JSON (بدون فایل)
+ POST /AskWithFiles - برای multipart/form-data (با فایل)
+
+
+
+
+
+
+
⚙️ گام ۷: اصلاح XAIServiceBase برای پردازش فایل
+
+
+ MODIFY
+ xAiApi/Providers/XAIServiceBase.cs
+
+
+
۷.۱ افزودن وابستگیهای جدید به Constructor:
+
+
private readonly IXAiDataProvider dataProvider;
+private readonly ILogger<XAIServiceBase> logger;
+private readonly XAiApiConfiguration configuration;
+private readonly XValidationProvider validationProvider;
+// ✅ وابستگیهای جدید:
+private readonly IFileContentExtractor fileContentExtractor;
+private readonly IXFileService fileService; // از xFileService
+
+protected XAIServiceBase(
+ IXAiDataProvider dataProvider,
+ ILogger<XAIServiceBase> logger,
+ XAiApiConfiguration configuration,
+ XValidationProvider validationProvider,
+ IFileContentExtractor fileContentExtractor, // ✅ جدید
+ IXFileService fileService, // ✅ جدید
+ string model = null
+)
+{
+ // ...
+ this.fileContentExtractor = fileContentExtractor;
+ this.fileService = fileService;
+ // ...
+}
+
+
۷.۲ پیادهسازی متد کمکی برای پردازش فایلها:
+
+
#region File Processing ...
+
+/// <summary>
+/// Process uploaded files and convert to ChatContents ...
+/// </summary>
+protected virtual async Task<IList<AIContent>> ProcessFilesAsync(
+ IFormFileCollection files,
+ string ownerId,
+ Guid messageId,
+ CancellationToken cancellationToken = default
+)
+{
+ var result = new List<AIContent>();
+
+ if (files == null || !files.Any())
+ {
+ return result;
+ }
+
+ foreach (var file in files)
+ {
+ if (file.Length == 0) continue;
+
+ var mimeType = file.ContentType;
+
+ // Check if it's an image (multi-modal)
+ if (IsImageFile(mimeType))
+ {
+ var imageData = await ReadStreamAsync(
+ file.OpenReadStream(),
+ cancellationToken
+ );
+ result.Add(new DataContent(imageData, mimeType));
+ }
+ // Text-based files
+ else if (fileContentExtractor.CanExtract(mimeType))
+ {
+ using var stream = file.OpenReadStream();
+ var content = await fileContentExtractor.ExtractAsync(
+ stream,
+ mimeType,
+ cancellationToken
+ );
+
+ // Add file content as context
+ var fileContext = $"[File: {file.FileName}]\n{content}\n[/File]";
+ result.Add(new TextContent(fileContext));
+ }
+ else
+ {
+ logger.LogWarning(
+ "Unsupported file type: {MimeType}",
+ mimeType
+ );
+ }
+ }
+
+ return result;
+}
+
+private bool IsImageFile(string mimeType)
+{
+ return mimeType?.StartsWith("image/") == true;
+}
+
+private async Task<byte[]> ReadStreamAsync(
+ Stream stream,
+ CancellationToken cancellationToken
+)
+{
+ using var memoryStream = new MemoryStream();
+ await stream.CopyToAsync(memoryStream, cancellationToken);
+ return memoryStream.ToArray();
+}
+
+#endregion
+
+
۷.۳ اصلاح متد AskAsync برای استفاده از فایلها:
+
+
public async Task<XAiMessageDto> AskAsync(
+ string prompt,
+ string ownerId,
+ Guid projectId,
+ Guid conversationId,
+ string connectionId = null,
+ IFormFileCollection files = null,
+ CancellationToken cancellationToken = default
+)
+{
+ // ... (کدهای قبلی validation و load project/conversation)
+
+ // Handle Memory ...
+ IList<ChatMessage> history = await PrepareMemory(
+ project: project,
+ messages: messages,
+ cancellationToken: cancellationToken
+ );
+
+ // Prepare and Add Prompt Message ...
+ var promptMessage = new XAiMessageDto
+ {
+ Content = prompt,
+ OwnerId = ownerId,
+ Role = XAiChatRole.User,
+ CreatedOn = DateTime.UtcNow,
+ ConversationId = conversationId,
+ ConversationTitle = conversation.Title
+ };
+
+ promptMessage = await dataProvider.AddMessage(
+ item: promptMessage,
+ connectionId: connectionId,
+ conversationId: conversationId,
+ cancellationToken: cancellationToken
+ );
+
+ // ✅ پردازش فایلها و افزودن به پیام
+ var fileContents = await ProcessFilesAsync(
+ files: files,
+ ownerId: ownerId,
+ messageId: promptMessage.Id,
+ cancellationToken: cancellationToken
+ );
+
+ // ✅ ذخیره Attachments در دیتابیس
+ if (files != null && files.Any())
+ {
+ await SaveFileAttachmentsAsync(
+ files: files,
+ ownerId: ownerId,
+ messageId: promptMessage.Id,
+ cancellationToken: cancellationToken
+ );
+ }
+
+ // ✅ ساخت ChatMessage با فایلهای ضمیمه
+ var promptChatMessage = promptMessage.ToChatMessages(fileContents);
+
+ // Ask Questions From LLM ...
+ var answer = await AskLLMAsync(
+ history: history,
+ prompt: promptChatMessage,
+ cancellationToken: cancellationToken
+ );
+
+ // ... (بقیه کد)
+}
+
+
+
+
+
🔌 گام ۸: اصلاح Extension برای تبدیل به ChatMessage
+
+
+ MODIFY
+ xAiModels/Extensions/XAiModelsExtensions.cs
+
+
+
// ❌ کد قبلی:
+public static ChatMessage ToChatMessages(
+ this XAiMessageDto source
+)
+{
+ // TODO: Handle Files Attached here ...
+ ChatMessage result = null;
+ if (!source.IsNullOrDefault())
+ {
+ result = new ChatMessage
+ {
+ AuthorName = ...,
+ Role = source.Role.ToChatRole(),
+ MessageId = source.Id.ToString(),
+ Contents = [new TextContent(source.Content)]
+ };
+ }
+ return result;
+}
+
+// ✅ کد جدید با پشتیبانی از فایل:
+public static ChatMessage ToChatMessages(
+ this XAiMessageDto source,
+ IList<AIContent> additionalContents = null
+)
+{
+ ChatMessage result = null;
+
+ if (!source.IsNullOrDefault())
+ {
+ // ساخت لیست Contents
+ var contents = new List<AIContent>();
+
+ // افزودن متن اصلی پیام
+ if (!string.IsNullOrWhiteSpace(source.Content))
+ {
+ contents.Add(new TextContent(source.Content));
+ }
+
+ // ✅ افزودن محتوای فایلهای ضمیمه
+ if (additionalContents != null && additionalContents.Any())
+ {
+ contents.AddRange(additionalContents);
+ }
+
+ result = new ChatMessage
+ {
+ AuthorName =
+ source.Role == XAiChatRole.User &&
+ !source.Owner.IsNullOrDefault()
+ ? source.Owner.GetFullname()
+ : string.Empty,
+ Role = source.Role.ToChatRole(),
+ MessageId = source.Id.ToString(),
+ Contents = contents // ✅ شامل متن + فایلها
+ };
+ }
+
+ return result;
+}
+
+
+ ✅ نکته کلیدی: با استفاده از AIContent به عنوان کلاس پایه،
+ میتوانیم هم TextContent (برای فایلهای متنی) و هم DataContent
+ (برای تصاویر) را در یک ChatMessage قرار دهیم. این رویکرد با استاندارد
+ Microsoft.Extensions.AI کاملاً سازگار است.
+
+
+
+
+
+
🔗 گام ۹: ثبت سرویسهای جدید در DI
+
+
+ MODIFY
+ xAiApi/Startup.cs
+
+
+
public void ConfigureServices(IServiceCollection services)
+{
+ // ... (کدهای قبلی)
+
+ // ✅ ثبت File Content Extractors
+ services.AddSingleton<IFileContentExtractor, PlainTextContentExtractor>();
+ services.AddSingleton<IFileContentExtractor, PdfContentExtractor>();
+ // services.AddSingleton<IFileContentExtractor, DocxContentExtractor>(); // آینده
+
+ // ✅ ثبت Composite Extractor
+ services.AddSingleton<IFileContentExtractor, CompositeFileContentExtractor>();
+
+ // ✅ ثبت File Attachment Service
+ services.AddScoped<IXAiFileAttachmentService, XAiFileAttachmentService>();
+
+ // ... (بقیه کدها)
+}
+
+
+ 💡 نکته: از AddSingleton برای Extractors استفاده میکنیم چون stateless هستند
+ و performance بهتری دارند.
+
+
+
+
+
+
🗄️ گام ۱۰: Migration پایگاه داده
+
+
اجرای دستورات EF Core:
+
+
# در Package Manager Console:
+Add-Migration AddFileAttachments -Context XAiApiDbContext
+Update-Database -Context XAiApiDbContext
+
+# یا در .NET CLI:
+dotnet ef migrations add AddFileAttachments --context XAiApiDbContext
+dotnet ef database update --context XAiApiDbContext
+
+
ساختار جدول جدید:
+
+
migrationBuilder.CreateTable(
+ name: "AiFileAttachments",
+ columns: table => new
+ {
+ Id = table.Column<Guid>(nullable: false),
+ Deleted = table.Column<bool>(nullable: false),
+ OwnerId = table.Column<string>(maxLength: 255, nullable: false),
+ MessageId = table.Column<Guid>(nullable: false),
+ FileId = table.Column<Guid>(nullable: false),
+ FileName = table.Column<string>(maxLength: 500, nullable: false),
+ MimeType = table.Column<string>(maxLength: 255, nullable: false),
+ FileSize = table.Column<long>(nullable: false),
+ ExtractedContent = table.Column<string>(nullable: true),
+ ContentHash = table.Column<string>(maxLength: 255, nullable: true),
+ Status = table.Column<int>(nullable: false),
+ ErrorMessage = table.Column<string>(nullable: true),
+ Order = table.Column<int>(nullable: false),
+ CreatedOn = table.Column<DateTime>(nullable: false),
+ UpdatedAt = table.Column<DateTime>(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AiFileAttachments", x => x.Id);
+ }
+);
+
+
+
+
+
🔄 گام ۱۱: جریان کامل پردازش
+
+
۱۱.۱ جریان پرسش با فایل ضمیمه:
+
+ 📤 Client: multipart/form-data
+ →
+ 🎮 Controller: Ask
+ →
+ ⚙️ Service: AskAsync
+ →
+ 🔍 Extract Content
+ →
+ 🎨 Convert to AIContent
+ →
+ 📎 Attach to ChatMessage
+ →
+ 🤖 LLM API
+
+
+
۱۱.۲ نمونه درخواست از کلاینت:
+
+
// JavaScript / Fetch API Example:
+const formData = new FormData();
+formData.append('Prompt', 'این فایل را تحلیل کن');
+formData.append('ProjectId', '...');
+formData.append('ConversationId', '...');
+
+// افزودن فایلها
+const fileInput = document.getElementById('fileInput');
+for (const file of fileInput.files) {
+ formData.append('files', file);
+}
+
+const response = await fetch('/DefaultAi/Ask', {
+ method: 'POST',
+ body: formData // ✅ Content-Type خودکار تنظیم میشود
+});
+
+const result = await response.json();
+
+
// cURL Example:
+curl -X POST "https://api.example.com/DefaultAi/Ask" \
+ -H "Authorization: Bearer YOUR_TOKEN" \
+ -F "Prompt=این فایل را تحلیل کن" \
+ -F "ProjectId=YOUR_PROJECT_ID" \
+ -F "ConversationId=YOUR_CONVERSATION_ID" \
+ -F "files=@/path/to/document.pdf" \
+ -F "files=@/path/to/image.png"
+
+
+
+
+
📋 گام ۱۲: خلاصه تغییرات
+
+
+
+ | ردیف |
+ فایل |
+ نوع تغییر |
+ توضیح |
+
+
+ | ۱ |
+ xAiModels/Models/Entities/XAiFileAttachment.cs |
+ NEW |
+ Entity جدید برای رابطه فایل و پیام |
+
+
+ | ۲ |
+ xAiModels/Models/Dtos/XAiFileAttachmentDto.cs |
+ NEW |
+ DTO متناظر |
+
+
+ | ۳ |
+ xAiService/Interfaces/IFileContentExtractor.cs |
+ NEW |
+ Interface برای Extractor |
+
+
+ | ۴ |
+ xAiService/Providers/PlainTextContentExtractor.cs |
+ NEW |
+ Extractor برای فایلهای متنی |
+
+
+ | ۵ |
+ xAiService/Providers/PdfContentExtractor.cs |
+ NEW |
+ Extractor برای PDF |
+
+
+ | ۶ |
+ xAiService/Providers/CompositeFileContentExtractor.cs |
+ NEW |
+ Composite Pattern برای Extractors |
+
+
+ | ۷ |
+ xAiApi/Controllers/XAiServiceControllerBase.cs |
+ MODIFY |
+ تغییر به [FromForm] و دریافت files |
+
+
+ | ۸ |
+ xAiApi/Providers/XAIServiceBase.cs |
+ MODIFY |
+ افزودن ProcessFilesAsync |
+
+
+ | ۹ |
+ xAiModels/Extensions/XAiModelsExtensions.cs |
+ MODIFY |
+ پشتیبانی از additionalContents |
+
+
+ | ۱۰ |
+ xAiApi/Startup.cs |
+ MODIFY |
+ ثبت سرویسهای جدید در DI |
+
+
+ | ۱۱ |
+ xAiApi/Database/XAiApiDbContext.cs |
+ MODIFY |
+ افزودن EntityRegistrar جدید |
+
+
+ | ۱۲ |
+ Migration جدید |
+ NEW |
+ ایجاد جدول AiFileAttachments |
+
+
+
+
+
✅ مزایای این طراحی:
+
+ - 🎯 سازگاری با معماری موجود: از الگوهای Repository, Provider, Enricher استفاده میکند
+ - 🔌 قابل گسترش: با اضافه کردن Extractor جدید، انواع فایل بیشتری پشتیبانی میشود
+ - ⚡ بهینه: محتوای استخراج شده cache میشود
+ - 🎨 Multi-modal Ready: از تصاویر و فایلهای متنی پشتیبانی میکند
+ - 🔒 امن: از xFileService موجود برای ذخیرهسازی استفاده میکند
+ - 📊 قابل ردیابی: هر فایل به پیام مرتبط است و metadata کامل ذخیره میشود
+
+
+
+
+
⚠️ موارد پیشنهادی برای فازهای بعدی:
+
+ - 📚 RAG Implementation: استفاده از
XAiDocument و VectorHelper موجود برای جستجوی معنایی
+ - 📄 DOCX/Excel Support: افزودن Extractor برای Office files
+ - 🔍 File Preview: API برای دریافت thumbnail و preview فایلها
+ - 📏 File Size Limits: پیکربندی حداکثر حجم فایل در
XAiApiConfiguration
+ - 🦠 Virus Scanning: اسکن فایلهای آپلود شده قبل از پردازش
+ - 📊 Analytics: آمار استفاده از فایلها در مکالمات
+
+
+
+
+ 💬 پیام به استاد: این طراحی کاملاً با معماری ماژولار پروژه شما سازگار است
+ و از الگوهای موجود (Repository, Provider, Enricher, DI Extensions) استفاده میکند.
+ پیادهسازی گام به گام این تغییرات، قابلیت قدرتمند File-Attached Messaging را به پروژه اضافه میکند
+ و زمینه را برای RAG پیشرفته در آینده فراهم میسازد. 🚀
+
+
+
+