1485 lines
53 KiB
HTML
1485 lines
53 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fa" dir="rtl">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>طراحی قابلیت File-Attached Messaging در xAiApi - فن آوران ساحر علم</title>
|
||
<style>
|
||
:root {
|
||
--primary: #1e3a8a;
|
||
--secondary: #3b82f6;
|
||
--accent: #f59e0b;
|
||
--success: #10b981;
|
||
--danger: #ef4444;
|
||
--warning: #f97316;
|
||
--bg-light: #f8fafc;
|
||
--bg-code: #1e293b;
|
||
--text-dark: #0f172a;
|
||
--text-muted: #64748b;
|
||
--border: #e2e8f0;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: 'Tahoma', 'Segoe UI', sans-serif;
|
||
background: linear-gradient(135deg, #f8fafc 0%, #e0e7ff 100%);
|
||
color: var(--text-dark);
|
||
line-height: 1.8;
|
||
padding: 20px;
|
||
}
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
border-radius: 16px;
|
||
box-shadow: 0 20px 60px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
}
|
||
.header {
|
||
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||
color: white;
|
||
padding: 40px;
|
||
text-align: center;
|
||
position: relative;
|
||
}
|
||
.header h1 { font-size: 2.2em; margin-bottom: 10px; position: relative; }
|
||
.header .subtitle { font-size: 1.1em; opacity: 0.95; position: relative; }
|
||
.meta-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
background: var(--bg-light);
|
||
padding: 15px 30px;
|
||
border-bottom: 2px solid var(--border);
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
}
|
||
.meta-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 0.9em;
|
||
color: var(--text-muted);
|
||
}
|
||
.meta-item strong { color: var(--primary); }
|
||
.content { padding: 40px; }
|
||
.section {
|
||
margin-bottom: 35px;
|
||
padding: 25px;
|
||
background: var(--bg-light);
|
||
border-radius: 12px;
|
||
border-right: 5px solid var(--secondary);
|
||
}
|
||
.section h2 {
|
||
color: var(--primary);
|
||
font-size: 1.6em;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 10px;
|
||
border-bottom: 2px solid var(--border);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
.section h3 {
|
||
color: var(--secondary);
|
||
font-size: 1.2em;
|
||
margin: 20px 0 12px;
|
||
}
|
||
.step {
|
||
background: white;
|
||
padding: 20px;
|
||
margin: 15px 0;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
border-right: 4px solid var(--accent);
|
||
}
|
||
.step-number {
|
||
display: inline-block;
|
||
background: var(--accent);
|
||
color: white;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
text-align: center;
|
||
line-height: 32px;
|
||
font-weight: bold;
|
||
margin-left: 10px;
|
||
}
|
||
.arch-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
.arch-card {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||
border-top: 4px solid var(--secondary);
|
||
transition: transform 0.3s;
|
||
}
|
||
.arch-card:hover { transform: translateY(-5px); }
|
||
.arch-card h4 {
|
||
color: var(--primary);
|
||
margin-bottom: 12px;
|
||
font-size: 1.15em;
|
||
}
|
||
.arch-card ul { list-style: none; padding-right: 0; }
|
||
.arch-card li {
|
||
padding: 6px 0;
|
||
padding-right: 20px;
|
||
position: relative;
|
||
font-size: 0.95em;
|
||
}
|
||
.arch-card li::before {
|
||
content: '▸';
|
||
position: absolute;
|
||
right: 0;
|
||
color: var(--accent);
|
||
font-weight: bold;
|
||
}
|
||
.tech-badge {
|
||
display: inline-block;
|
||
background: var(--secondary);
|
||
color: white;
|
||
padding: 4px 12px;
|
||
border-radius: 20px;
|
||
font-size: 0.85em;
|
||
margin: 3px;
|
||
}
|
||
.tech-badge.primary { background: var(--primary); }
|
||
.tech-badge.success { background: var(--success); }
|
||
.tech-badge.accent { background: var(--accent); }
|
||
.tech-badge.danger { background: var(--danger); }
|
||
.tech-badge.warning { background: var(--warning); }
|
||
pre {
|
||
background: var(--bg-code);
|
||
color: #e2e8f0;
|
||
padding: 18px;
|
||
border-radius: 8px;
|
||
overflow-x: auto;
|
||
direction: ltr;
|
||
text-align: left;
|
||
font-family: 'Consolas', 'Courier New', monospace;
|
||
font-size: 0.85em;
|
||
margin: 15px 0;
|
||
border-right: 4px solid var(--accent);
|
||
}
|
||
code {
|
||
background: #fef3c7;
|
||
color: #92400e;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-family: 'Consolas', monospace;
|
||
font-size: 0.9em;
|
||
direction: ltr;
|
||
display: inline-block;
|
||
}
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin: 15px 0;
|
||
background: white;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||
}
|
||
th {
|
||
background: var(--primary);
|
||
color: white;
|
||
padding: 12px;
|
||
text-align: right;
|
||
font-weight: bold;
|
||
}
|
||
td {
|
||
padding: 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
tr:hover { background: var(--bg-light); }
|
||
.flow-diagram {
|
||
background: white;
|
||
padding: 25px;
|
||
border-radius: 10px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}
|
||
.flow-step {
|
||
display: inline-block;
|
||
background: var(--secondary);
|
||
color: white;
|
||
padding: 10px 20px;
|
||
border-radius: 8px;
|
||
margin: 5px;
|
||
font-size: 0.9em;
|
||
}
|
||
.flow-arrow {
|
||
display: inline-block;
|
||
color: var(--accent);
|
||
font-size: 1.5em;
|
||
margin: 0 8px;
|
||
vertical-align: middle;
|
||
}
|
||
.alert {
|
||
padding: 15px 20px;
|
||
border-radius: 8px;
|
||
margin: 15px 0;
|
||
border-right: 4px solid;
|
||
}
|
||
.alert-info {
|
||
background: #dbeafe;
|
||
border-color: var(--secondary);
|
||
color: #1e40af;
|
||
}
|
||
.alert-success {
|
||
background: #d1fae5;
|
||
border-color: var(--success);
|
||
color: #065f46;
|
||
}
|
||
.alert-warning {
|
||
background: #fef3c7;
|
||
border-color: var(--accent);
|
||
color: #92400e;
|
||
}
|
||
.alert-danger {
|
||
background: #fee2e2;
|
||
border-color: var(--danger);
|
||
color: #991b1b;
|
||
}
|
||
.footer {
|
||
background: var(--primary);
|
||
color: white;
|
||
padding: 25px;
|
||
text-align: center;
|
||
margin-top: 40px;
|
||
}
|
||
.footer p { margin: 5px 0; }
|
||
.highlight {
|
||
background: linear-gradient(120deg, #fef3c7 0%, #fef3c7 100%);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-weight: bold;
|
||
}
|
||
.toc {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
margin-bottom: 25px;
|
||
border: 2px solid var(--border);
|
||
}
|
||
.toc h3 { color: var(--primary); margin-bottom: 15px; }
|
||
.toc ol { padding-right: 25px; }
|
||
.toc li { padding: 6px 0; }
|
||
.toc a {
|
||
color: var(--secondary);
|
||
text-decoration: none;
|
||
transition: color 0.2s;
|
||
}
|
||
.toc a:hover { color: var(--primary); text-decoration: underline; }
|
||
.file-change {
|
||
background: #f0f9ff;
|
||
border-right: 4px solid var(--secondary);
|
||
padding: 15px;
|
||
margin: 10px 0;
|
||
border-radius: 8px;
|
||
}
|
||
.file-change .path {
|
||
font-family: 'Consolas', monospace;
|
||
color: var(--primary);
|
||
font-weight: bold;
|
||
direction: ltr;
|
||
display: inline-block;
|
||
}
|
||
.badge-new {
|
||
background: var(--success);
|
||
color: white;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-size: 0.75em;
|
||
margin-right: 8px;
|
||
}
|
||
.badge-modify {
|
||
background: var(--warning);
|
||
color: white;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-size: 0.75em;
|
||
margin-right: 8px;
|
||
}
|
||
.badge-new-entity {
|
||
background: #8b5cf6;
|
||
color: white;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-size: 0.75em;
|
||
margin-right: 8px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
|
||
<div class="header">
|
||
<h1>📎 طراحی قابلیت File-Attached Messaging</h1>
|
||
<div class="subtitle">افزودن قابلیت ضمیمه کردن فایل به پیامها برای استفاده در تولید پاسخ توسط مدل هوش مصنوعی</div>
|
||
</div>
|
||
|
||
<div class="meta-bar">
|
||
<div class="meta-item">👨💻 <strong>توسعهدهنده:</strong> هادی خزاعی اصل</div>
|
||
<div class="meta-item">🏢 <strong>شرکت:</strong> فن آوران ساحر علم</div>
|
||
<div class="meta-item">📅 <strong>تاریخ تهیه مستند:</strong> چهارشنبه ۸ مهر ۱۴۰۵</div>
|
||
<div class="meta-item">📦 <strong>پروژه:</strong> xAiApi</div>
|
||
</div>
|
||
|
||
<div class="content">
|
||
|
||
<div class="toc">
|
||
<h3>📑 فهرست مطالب</h3>
|
||
<ol>
|
||
<li><a href="#analysis">تحلیل وضعیت فعلی و TODO های موجود</a></li>
|
||
<li><a href="#strategy">انتخاب استراتژی مناسب</a></li>
|
||
<li><a href="#architecture">معماری پیشنهادی</a></li>
|
||
<li><a href="#step1">گام ۱: افزودن Entity برای رابطه فایل و پیام</a></li>
|
||
<li><a href="#step2">گام ۲: ایجاد File Content Extractor</a></li>
|
||
<li><a href="#step3">گام ۳: اصلاح Controller برای دریافت فایل</a></li>
|
||
<li><a href="#step4">گام ۴: اصلاح XAIServiceBase برای پردازش فایل</a></li>
|
||
<li><a href="#step5">گام ۵: اصلاح Extension برای تبدیل به ChatMessage</a></li>
|
||
<li><a href="#step6">گام ۶: ثبت سرویسهای جدید در DI</a></li>
|
||
<li><a href="#step7">گام ۷: Migration پایگاه داده</a></li>
|
||
<li><a href="#flow">جریان کامل پردازش</a></li>
|
||
<li><a href="#summary">خلاصه تغییرات</a></li>
|
||
</ol>
|
||
</div>
|
||
|
||
<!-- Section 1: Analysis -->
|
||
<div class="section" id="analysis">
|
||
<h2>🔍 گام ۱: تحلیل وضعیت فعلی و TODO های موجود</h2>
|
||
<p>
|
||
با بررسی دقیق کد پروژه، مشخص شد که <strong>زیرساخت اولیه</strong> برای این قابلیت از قبل طراحی شده
|
||
و فقط نیاز به تکمیل دارد. در نقاط مختلف کد، <span class="highlight">TODO های مشخصی</span> وجود دارد:
|
||
</p>
|
||
|
||
<div class="arch-grid">
|
||
<div class="arch-card">
|
||
<h4>📍 TODO در Controller</h4>
|
||
<p style="font-size: 0.9em; color: var(--text-muted);">
|
||
در <code>XAiServiceControllerBase.cs</code> در متدهای <code>Ask</code> و <code>AskStream</code>:
|
||
</p>
|
||
<pre style="font-size: 0.75em;">// TODO: Reading Fiels Form Collection
|
||
// and Attach it ...
|
||
var result = await aiService.AskAsync(
|
||
files: null, // ❌ null ارسال میشود
|
||
...
|
||
);</pre>
|
||
</div>
|
||
<div class="arch-card">
|
||
<h4>📍 TODO در Service</h4>
|
||
<p style="font-size: 0.9em; color: var(--text-muted);">
|
||
در <code>XAIServiceBase.cs</code> در متدهای <code>AskAsync</code> و <code>AskAsEnumerable</code>:
|
||
</p>
|
||
<pre style="font-size: 0.75em;">// TODO: Parse Files ...
|
||
// for Attached them into Prompt
|
||
// Message ...</pre>
|
||
</div>
|
||
<div class="arch-card">
|
||
<h4>📍 TODO در Extension</h4>
|
||
<p style="font-size: 0.9em; color: var(--text-muted);">
|
||
در <code>XAiModelsExtensions.cs</code> در متد <code>ToChatMessages</code>:
|
||
</p>
|
||
<pre style="font-size: 0.75em;">// TODO: Handle Files Attached here ...
|
||
ChatMessage result = null;</pre>
|
||
</div>
|
||
<div class="arch-card">
|
||
<h4>📍 Entity آماده</h4>
|
||
<p style="font-size: 0.9em; color: var(--text-muted);">
|
||
Entity <code>XAiDocument</code> از قبل طراحی شده و شامل فیلدهای Vector, ContentHash, EmbeddingModel است:
|
||
</p>
|
||
<pre style="font-size: 0.75em;">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; }
|
||
}</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert alert-info">
|
||
<strong>💡 نتیجهگیری:</strong> زیرساخت طراحی شده است، فقط نیاز به پیادهسازی و اتصال اجزا داریم.
|
||
این یک نشانه عالی از طراحی آیندهنگرانه معماری شماست! 🎯
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 2: Strategy -->
|
||
<div class="section" id="strategy">
|
||
<h2>🎯 گام ۲: انتخاب استراتژی مناسب</h2>
|
||
<p>
|
||
برای افزودن قابلیت فایل به پیامها، سه استراتژی اصلی وجود دارد. با توجه به معماری پروژه و Entity آماده <code>XAiDocument</code>،
|
||
<strong>استراتژی ترکیبی</strong> پیشنهاد میشود:
|
||
</p>
|
||
|
||
<table>
|
||
<tr>
|
||
<th>استراتژی</th>
|
||
<th>توضیح</th>
|
||
<th>کاربرد</th>
|
||
<th>پیچیدگی</th>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tech-badge success">Level 1</span> Inline Context</td>
|
||
<td>محتوای فایل مستقیماً به prompt اضافه شود</td>
|
||
<td>فایلهای متنی کوچک (TXT, MD, CSV)</td>
|
||
<td>⭐ ساده</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tech-badge accent">Level 2</span> Multi-modal</td>
|
||
<td>تصاویر به صورت DataContent ارسال شوند</td>
|
||
<td>فایلهای تصویری (PNG, JPG)</td>
|
||
<td>⭐⭐ متوسط</td>
|
||
</tr>
|
||
<tr>
|
||
<td><span class="tech-badge primary">Level 3</span> RAG</td>
|
||
<td>فایل Embed شده و بخشهای مرتبط جستجو شوند</td>
|
||
<td>فایلهای بزرگ (PDF, DOCX)</td>
|
||
<td>⭐⭐⭐ پیچیده</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<div class="alert alert-success">
|
||
<strong>✅ پیشنهاد:</strong> ابتدا <strong>Level 1 و Level 2</strong> را پیادهسازی میکنیم که بیشترین کاربرد را دارند.
|
||
Level 3 (RAG) در آینده با استفاده از Entity <code>XAiDocument</code> و سرویس Embedding موجود قابل افزودن است.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section 3: Architecture -->
|
||
<div class="section" id="architecture">
|
||
<h2>🏗️ گام ۳: معماری پیشنهادی</h2>
|
||
|
||
<h3>۳.۱ اجزای جدید مورد نیاز:</h3>
|
||
<div class="arch-grid">
|
||
<div class="arch-card">
|
||
<h4>📄 XAiFileAttachment Entity</h4>
|
||
<ul>
|
||
<li>رابطه بین Message و File</li>
|
||
<li>ذخیره Metadata فایل</li>
|
||
<li>ContentHash برای Deduplication</li>
|
||
</ul>
|
||
</div>
|
||
<div class="arch-card">
|
||
<h4>🔧 IFileContentExtractor</h4>
|
||
<ul>
|
||
<li>استخراج متن از PDF</li>
|
||
<li>استخراج متن از DOCX</li>
|
||
<li>استخراج متن از TXT/MD</li>
|
||
<li>استخراج متن از CSV/Excel</li>
|
||
</ul>
|
||
</div>
|
||
<div class="arch-card">
|
||
<h4>🎨 IFileToChatContentConverter</h4>
|
||
<ul>
|
||
<li>تبدیل فایل به ChatContent</li>
|
||
<li>پشتیبانی از TextContent</li>
|
||
<li>پشتیبانی از DataContent (Image)</li>
|
||
</ul>
|
||
</div>
|
||
<div class="arch-card">
|
||
<h4>📦 XAiFileAttachmentService</h4>
|
||
<ul>
|
||
<li>مدیریت آپلود فایل</li>
|
||
<li>ارتباط با xFileService</li>
|
||
<li>ذخیره Attachments</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<h3>۳.۲ جریان کلی پردازش:</h3>
|
||
<div class="flow-diagram">
|
||
<span class="flow-step">📤 Upload Files</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">💾 Save to Storage</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🔍 Extract Content</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🎨 Convert to ChatContent</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">📎 Attach to Message</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🤖 Send to LLM</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 1: Entity -->
|
||
<div class="section" id="step1">
|
||
<h2>📝 گام ۴: افزودن Entity برای رابطه فایل و پیام</h2>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-new-entity">NEW ENTITY</span>
|
||
<span class="path">xAiModels/Models/Entities/XAiFileAttachment.cs</span>
|
||
</div>
|
||
|
||
<pre>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
|
||
}
|
||
}</pre>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-new-entity">NEW DTO</span>
|
||
<span class="path">xAiModels/Models/Dtos/XAiFileAttachmentDto.cs</span>
|
||
</div>
|
||
|
||
<pre>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; }
|
||
}
|
||
}</pre>
|
||
|
||
<div class="alert alert-info">
|
||
<strong>💡 نکته طراحی:</strong> با ذخیره <code>ExtractedContent</code> در دیتابیس، از پردازش مجدد فایل در هر پرسش جلوگیری میکنیم
|
||
و performance بهینه میشود.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 2: File Content Extractor -->
|
||
<div class="section" id="step2">
|
||
<h2>🔧 گام ۵: ایجاد File Content Extractor</h2>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-new">NEW</span>
|
||
<span class="path">xAiService/Interfaces/IFileContentExtractor.cs</span>
|
||
</div>
|
||
|
||
<pre>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
|
||
);
|
||
}
|
||
}</pre>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-new">NEW</span>
|
||
<span class="path">xAiService/Providers/PlainTextContentExtractor.cs</span>
|
||
</div>
|
||
|
||
<pre>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();
|
||
}
|
||
}
|
||
}</pre>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-new">NEW</span>
|
||
<span class="path">xAiService/Providers/PdfContentExtractor.cs</span>
|
||
</div>
|
||
|
||
<pre>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;
|
||
}
|
||
}
|
||
}</pre>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-new">NEW</span>
|
||
<span class="path">xAiService/Providers/CompositeFileContentExtractor.cs</span>
|
||
</div>
|
||
|
||
<pre>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
|
||
);
|
||
}
|
||
}
|
||
}</pre>
|
||
|
||
<div class="alert alert-warning">
|
||
<strong>⚠️ پکیجهای NuGet مورد نیاز:</strong>
|
||
<ul style="padding-right: 25px; margin-top: 10px;">
|
||
<li><code>UglyToad.PdfPig</code> - برای استخراج متن از PDF</li>
|
||
<li><code>DocumentFormat.OpenXml</code> - برای استخراج متن از DOCX (آینده)</li>
|
||
<li><code>ExcelDataReader</code> - برای استخراج متن از Excel (آینده)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 3: Controller -->
|
||
<div class="section" id="step3">
|
||
<h2>🎮 گام ۶: اصلاح Controller برای دریافت فایل</h2>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-modify">MODIFY</span>
|
||
<span class="path">xAiApi/Controllers/XAiServiceControllerBase.cs</span>
|
||
</div>
|
||
|
||
<p><strong>تغییرات در متد <code>Ask</code>:</strong></p>
|
||
|
||
<pre>// ❌ کد قبلی:
|
||
[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);
|
||
}
|
||
}</pre>
|
||
|
||
<p><strong>تغییر مشابه برای متد <code>AskStream</code>:</strong></p>
|
||
|
||
<pre>[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
|
||
);
|
||
// ...
|
||
}</pre>
|
||
|
||
<div class="alert alert-info">
|
||
<strong>💡 نکته مهم:</strong> با تغییر از <code>[FromBody]</code> به <code>[FromForm]</code>،
|
||
امکان ارسال همزمان فایل و JSON فراهم میشود. توجه داشته باشید که <code>XAiResponseRequest</code>
|
||
باید به صورت <code>multipart/form-data</code> ارسال شود.
|
||
</div>
|
||
|
||
<div class="alert alert-warning">
|
||
<strong>⚠️ سازگاری با کلاینتهای موجود:</strong> اگر میخواهید کلاینتهای فعلی که JSON ارسال میکنند
|
||
همچنان کار کنند، میتوانید <strong>دو endpoint مجزا</strong> ایجاد کنید:
|
||
<ul style="padding-right: 25px; margin-top: 10px;">
|
||
<li><code>POST /Ask</code> - برای JSON (بدون فایل)</li>
|
||
<li><code>POST /AskWithFiles</code> - برای multipart/form-data (با فایل)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 4: Service -->
|
||
<div class="section" id="step4">
|
||
<h2>⚙️ گام ۷: اصلاح XAIServiceBase برای پردازش فایل</h2>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-modify">MODIFY</span>
|
||
<span class="path">xAiApi/Providers/XAIServiceBase.cs</span>
|
||
</div>
|
||
|
||
<p><strong>۷.۱ افزودن وابستگیهای جدید به Constructor:</strong></p>
|
||
|
||
<pre>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;
|
||
// ...
|
||
}</pre>
|
||
|
||
<p><strong>۷.۲ پیادهسازی متد کمکی برای پردازش فایلها:</strong></p>
|
||
|
||
<pre>#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</pre>
|
||
|
||
<p><strong>۷.۳ اصلاح متد <code>AskAsync</code> برای استفاده از فایلها:</strong></p>
|
||
|
||
<pre>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
|
||
);
|
||
|
||
// ... (بقیه کد)
|
||
}</pre>
|
||
</div>
|
||
|
||
<!-- Step 5: Extension -->
|
||
<div class="section" id="step5">
|
||
<h2>🔌 گام ۸: اصلاح Extension برای تبدیل به ChatMessage</h2>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-modify">MODIFY</span>
|
||
<span class="path">xAiModels/Extensions/XAiModelsExtensions.cs</span>
|
||
</div>
|
||
|
||
<pre>// ❌ کد قبلی:
|
||
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;
|
||
}</pre>
|
||
|
||
<div class="alert alert-success">
|
||
<strong>✅ نکته کلیدی:</strong> با استفاده از <code>AIContent</code> به عنوان کلاس پایه،
|
||
میتوانیم هم <code>TextContent</code> (برای فایلهای متنی) و هم <code>DataContent</code>
|
||
(برای تصاویر) را در یک <code>ChatMessage</code> قرار دهیم. این رویکرد با استاندارد
|
||
<code>Microsoft.Extensions.AI</code> کاملاً سازگار است.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 6: DI -->
|
||
<div class="section" id="step6">
|
||
<h2>🔗 گام ۹: ثبت سرویسهای جدید در DI</h2>
|
||
|
||
<div class="file-change">
|
||
<span class="badge-modify">MODIFY</span>
|
||
<span class="path">xAiApi/Startup.cs</span>
|
||
</div>
|
||
|
||
<pre>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>();
|
||
|
||
// ... (بقیه کدها)
|
||
}</pre>
|
||
|
||
<div class="alert alert-info">
|
||
<strong>💡 نکته:</strong> از <code>AddSingleton</code> برای Extractors استفاده میکنیم چون stateless هستند
|
||
و performance بهتری دارند.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 7: Migration -->
|
||
<div class="section" id="step7">
|
||
<h2>🗄️ گام ۱۰: Migration پایگاه داده</h2>
|
||
|
||
<p><strong>اجرای دستورات EF Core:</strong></p>
|
||
|
||
<pre># در 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</pre>
|
||
|
||
<p><strong>ساختار جدول جدید:</strong></p>
|
||
|
||
<pre>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);
|
||
}
|
||
);</pre>
|
||
</div>
|
||
|
||
<!-- Flow -->
|
||
<div class="section" id="flow">
|
||
<h2>🔄 گام ۱۱: جریان کامل پردازش</h2>
|
||
|
||
<h3>۱۱.۱ جریان پرسش با فایل ضمیمه:</h3>
|
||
<div class="flow-diagram">
|
||
<span class="flow-step">📤 Client: multipart/form-data</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🎮 Controller: Ask</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">⚙️ Service: AskAsync</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🔍 Extract Content</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🎨 Convert to AIContent</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">📎 Attach to ChatMessage</span>
|
||
<span class="flow-arrow">→</span>
|
||
<span class="flow-step">🤖 LLM API</span>
|
||
</div>
|
||
|
||
<h3>۱۱.۲ نمونه درخواست از کلاینت:</h3>
|
||
|
||
<pre>// 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();</pre>
|
||
|
||
<pre>// 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"</pre>
|
||
</div>
|
||
|
||
<!-- Summary -->
|
||
<div class="section" id="summary">
|
||
<h2>📋 گام ۱۲: خلاصه تغییرات</h2>
|
||
|
||
<table>
|
||
<tr>
|
||
<th>ردیف</th>
|
||
<th>فایل</th>
|
||
<th>نوع تغییر</th>
|
||
<th>توضیح</th>
|
||
</tr>
|
||
<tr>
|
||
<td>۱</td>
|
||
<td><code>xAiModels/Models/Entities/XAiFileAttachment.cs</code></td>
|
||
<td><span class="badge-new-entity">NEW</span></td>
|
||
<td>Entity جدید برای رابطه فایل و پیام</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۲</td>
|
||
<td><code>xAiModels/Models/Dtos/XAiFileAttachmentDto.cs</code></td>
|
||
<td><span class="badge-new-entity">NEW</span></td>
|
||
<td>DTO متناظر</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۳</td>
|
||
<td><code>xAiService/Interfaces/IFileContentExtractor.cs</code></td>
|
||
<td><span class="badge-new">NEW</span></td>
|
||
<td>Interface برای Extractor</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۴</td>
|
||
<td><code>xAiService/Providers/PlainTextContentExtractor.cs</code></td>
|
||
<td><span class="badge-new">NEW</span></td>
|
||
<td>Extractor برای فایلهای متنی</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۵</td>
|
||
<td><code>xAiService/Providers/PdfContentExtractor.cs</code></td>
|
||
<td><span class="badge-new">NEW</span></td>
|
||
<td>Extractor برای PDF</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۶</td>
|
||
<td><code>xAiService/Providers/CompositeFileContentExtractor.cs</code></td>
|
||
<td><span class="badge-new">NEW</span></td>
|
||
<td>Composite Pattern برای Extractors</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۷</td>
|
||
<td><code>xAiApi/Controllers/XAiServiceControllerBase.cs</code></td>
|
||
<td><span class="badge-modify">MODIFY</span></td>
|
||
<td>تغییر به [FromForm] و دریافت files</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۸</td>
|
||
<td><code>xAiApi/Providers/XAIServiceBase.cs</code></td>
|
||
<td><span class="badge-modify">MODIFY</span></td>
|
||
<td>افزودن ProcessFilesAsync</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۹</td>
|
||
<td><code>xAiModels/Extensions/XAiModelsExtensions.cs</code></td>
|
||
<td><span class="badge-modify">MODIFY</span></td>
|
||
<td>پشتیبانی از additionalContents</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۱۰</td>
|
||
<td><code>xAiApi/Startup.cs</code></td>
|
||
<td><span class="badge-modify">MODIFY</span></td>
|
||
<td>ثبت سرویسهای جدید در DI</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۱۱</td>
|
||
<td><code>xAiApi/Database/XAiApiDbContext.cs</code></td>
|
||
<td><span class="badge-modify">MODIFY</span></td>
|
||
<td>افزودن EntityRegistrar جدید</td>
|
||
</tr>
|
||
<tr>
|
||
<td>۱۲</td>
|
||
<td>Migration جدید</td>
|
||
<td><span class="badge-new">NEW</span></td>
|
||
<td>ایجاد جدول AiFileAttachments</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<div class="alert alert-success">
|
||
<strong>✅ مزایای این طراحی:</strong>
|
||
<ul style="padding-right: 25px; margin-top: 10px;">
|
||
<li>🎯 <strong>سازگاری با معماری موجود:</strong> از الگوهای Repository, Provider, Enricher استفاده میکند</li>
|
||
<li>🔌 <strong>قابل گسترش:</strong> با اضافه کردن Extractor جدید، انواع فایل بیشتری پشتیبانی میشود</li>
|
||
<li>⚡ <strong>بهینه:</strong> محتوای استخراج شده cache میشود</li>
|
||
<li>🎨 <strong>Multi-modal Ready:</strong> از تصاویر و فایلهای متنی پشتیبانی میکند</li>
|
||
<li>🔒 <strong>امن:</strong> از xFileService موجود برای ذخیرهسازی استفاده میکند</li>
|
||
<li>📊 <strong>قابل ردیابی:</strong> هر فایل به پیام مرتبط است و metadata کامل ذخیره میشود</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="alert alert-warning">
|
||
<strong>⚠️ موارد پیشنهادی برای فازهای بعدی:</strong>
|
||
<ul style="padding-right: 25px; margin-top: 10px;">
|
||
<li>📚 <strong>RAG Implementation:</strong> استفاده از <code>XAiDocument</code> و <code>VectorHelper</code> موجود برای جستجوی معنایی</li>
|
||
<li>📄 <strong>DOCX/Excel Support:</strong> افزودن Extractor برای Office files</li>
|
||
<li>🔍 <strong>File Preview:</strong> API برای دریافت thumbnail و preview فایلها</li>
|
||
<li>📏 <strong>File Size Limits:</strong> پیکربندی حداکثر حجم فایل در <code>XAiApiConfiguration</code></li>
|
||
<li>🦠 <strong>Virus Scanning:</strong> اسکن فایلهای آپلود شده قبل از پردازش</li>
|
||
<li>📊 <strong>Analytics:</strong> آمار استفاده از فایلها در مکالمات</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="alert alert-info">
|
||
<strong>💬 پیام به استاد:</strong> این طراحی کاملاً با معماری ماژولار پروژه شما سازگار است
|
||
و از الگوهای موجود (Repository, Provider, Enricher, DI Extensions) استفاده میکند.
|
||
پیادهسازی گام به گام این تغییرات، قابلیت قدرتمند File-Attached Messaging را به پروژه اضافه میکند
|
||
و زمینه را برای RAG پیشرفته در آینده فراهم میسازد. 🚀
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<p><strong>👨💻 توسعهدهنده:</strong> هادی خزاعی اصل</p>
|
||
<p><strong>🏢 شرکت:</strong> فن آوران ساحر علم</p>
|
||
<p><strong>📅 تاریخ تهیه مستند:</strong> چهارشنبه ۸ مهر ۱۴۰۵</p>
|
||
<p style="margin-top: 15px; opacity: 0.8; font-size: 0.9em;">
|
||
📎 طراحی قابلیت File-Attached Messaging در xAiApi - تمامی حقوق محفوظ است
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</body>
|
||
</html> |