سلام! من Elesoft Chat (السافت چت) هستم.
کدی که ارسال کردید، یک کلاس کمکی (Helper Class) بسیار کاربردی و استاندارد به زبان C# است که در فضای نام (
Namespace) ToolsOfElesoftV2025.Models.Ai قرار دارد. وظیفه اصلی این کد، برقراری ارتباط با سرویس هوش مصنوعی (که ظاهراً مبتنی بر مدلهای جمنای گوگل و از طریق یک اسکریپت PHP در سرور api-ai.elft.ir پیادهسازی شده) است.در ادامه، بخش به بخش این کد را به زبان ساده و فنی برای شما شرح میدهم:
۱. مدل دادهای پیام (AiMessage)
public class AiMessage
{
public string Role { get; set; } = "user"; // فقط "user" یا "model" (فرمت Gemini)
public string Text { get; set; } = "";
}
- این کلاس ساختار هر پیام در گفتگو را مشخص میکند.
Role: مشخص میکند که فرستنده پیام کاربر است (user) یا هوش مصنوعی (model)؛ این دقیقاً مطابق با استانداردهای API گوگل جمنای است.Text: متن پیام.
۲. متد استریمینگ (GetFromAiStreamAsync)
این مهمترین متد این کلاس است که پاسخ هوش مصنوعی را بهصورت تکهتکه (Stream) و لحظهای دریافت میکند (شبیه به حالت تایپ شدن متن توسط ربات).
- پارامترها:
- لیستی از پیامها (
messages) برای حفظ تاریخچه گفتگو. - تنظیمات پیشرفته مثل تفکر عمیق (
think)، جستجوی وب (search)، و ارسال تصویر (hasImageبه همراه اطلاعات بیسکد تصویر). - نحوه عملکرد:
- اطلاعات را به فرمت JSON تبدیل کرده و با استفاده از
HttpClientبه آدرسhttps://api-ai.elft.ir/ai.phpارسال میکند. - پاسخ را بهصورت استریم (جریان داده) میخواند تا کاربر مجبور نباشد منتظر بماند تا کل پاسخ آماده شود.
- مدیریت خطاهای سرور: متد هوشمندانه به دنبال یک نشانهی خاص به نام
\u0001ERR\u0001میگردد. اگر سرور PHP در حین استریم کردن خطایی بفرستد، این کد آن را تشخیص داده، متن خطا را استخراج کرده و به کاربر نمایش میدهد (و از فرستادن کدهای اضافی جلوگیری میکند). - از یک متغیر بافر (
carry) استفاده میکند تا اگر نشانهی خطا دقیقاً بین دو تکه (Chunk) از دادهها تقسیم شد، آن را گم نکند و بهدرستی تشخیص دهد.
۳. متدهای غیر استریم (GetFromAiAsync)
برای زمانهایی که نیازی به نمایش لحظهای متن نداریم و کل پاسخ را یکجا میخواهیم:
- نسخهی اصلی: تمام تکههای دریافتی از متد استریم را با استفاده از
StringBuilderبه هم میچسباند و متن نهایی و کامل را برمیگرداند (Task<string>). - نسخهی سادهشده (Overload): یک متد کمکی که فقط یک رشته متن (
string text) بهعنوان ورودی میگیرد، آن را تبدیل به یک شیءAiMessageبا نقشuserمیکند و به متد اصلی میفرستد. این کار برای سازگاری با کدهای قدیمیتر پروژه گذاشته شده است.
۴. تولید خودکار عنوان چت (GetChatTitleAsync)
این متد یک قابلیت بسیار جذاب و کاربردی برای برنامههای چت (مثل ChatGPT) است:
- کاربرد: وقتی کاربر اولین پیام خود را ارسال میکند، این متد یک درخواست سبک و سریع به هوش مصنوعی میفرستد و از آن میخواهد که یک عنوان کوتاه (حداکثر ۶ کلمهای) متناسب با صحبت کاربر پیشنهاد دهد.
- پاکسازی خروجی:
- ابتدا پاسخ هوش مصنوعی را
Trimمیکند (فاصلههای اضافی را میگیرد). - با استفاده از
Regexکلماتی مثلTitle:یاعنوان:را اگر هوش مصنوعی به اشتباه در ابتدا آورده باشد، حذف میکند. - علامتهای نگارشی اضافی و نقلقولها (
",',«,») را از دور متن پاک میکند. - اگر طول عنوان بیشتر از ۸۰ کاراکتر شد، آن را برش میزند تا همیشه یک عنوان استاندارد و زیبا تحویل دهد.
💡 جمعبندی
این کد یک پیادهسازی بسیار حرفهای، بهینه و ایمن در C# است که از قابلیتهای مدرن این زبان (مثل
IAsyncEnumerable برای استریمینگ و CancellationToken برای لغو درخواستها) بهره میبرد و بهخوبی میتواند با بکاند PHP شما ارتباط برقرار کند.اگر سوالی در موردجای خاصی از این کد یا نحوه استفاده از آن دارید، بفرمایید تا بیشتر توضیح دهم!
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
namespace ToolsOfElesoftV2025.Models.Ai
{
public class AiMessage
{
public string Role { get; set; } = "user"; // فقط "user" یا "model" (فرمت Gemini)
public string Text { get; set; } = "";
}
public static class Api
{
private const string ErrMarker = "\u0001ERR\u0001";
/// <summary>
/// نسخهی استریم: تکههای متن رو همون لحظه که از PHP/Gemini میرسن برمیگردونه.
/// think/search/hasImage تعیین میکنن سمت PHP از کدوم مدل و ابزار استفاده بشه.
/// imageBase64/imageMimeType در صورت وجود، بهصورت inline_data به پیام آخر (پیام فعلی کاربر) اضافه میشن.
/// </summary>
public static async IAsyncEnumerable<string> GetFromAiStreamAsync(
List<AiMessage> messages,
bool think = false,
bool search = false,
bool hasImage = false,
string? imageBase64 = null,
string? imageMimeType = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var url = "https://api-ai.elft.ir/ai.php"; // دامنه خودت رو بزن
using var client = new HttpClient();
client.Timeout = TimeSpan.FromMinutes(3);
var data = new
{
messages = messages.Select(m => new { role = m.Role, text = m.Text }),
think = think,
search = search,
hasImage = hasImage,
image = (hasImage && !string.IsNullOrEmpty(imageBase64))
? new { mimeType = imageMimeType ?? "image/jpeg", data = imageBase64 }
: null
};
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = new StringContent(
Newtonsoft.Json.JsonConvert.SerializeObject(data),
Encoding.UTF8,
"application/json")
};
using var response = await client.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken);
if (!response.IsSuccessStatusCode)
{
yield return $"HTTP Error: {response.StatusCode}";
yield break;
}
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using var reader = new StreamReader(stream, Encoding.UTF8);
var buffer = new char[512];
var carry = ""; // بخش انتهایی که ممکنه شروعِ نشانهی خطا باشه، تا chunk بعدی نگه میداریم
while (!reader.EndOfStream)
{
int readCount = await reader.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken);
if (readCount == 0) continue;
var combined = carry + new string(buffer, 0, readCount);
carry = "";
var markerIndex = combined.IndexOf(ErrMarker, StringComparison.Ordinal);
if (markerIndex >= 0)
{
var before = combined.Substring(0, markerIndex);
var errMsg = combined.Substring(markerIndex + ErrMarker.Length);
if (!string.IsNullOrEmpty(before))
yield return before;
yield return $"\n\n[خطا در دریافت پاسخ: {errMsg.Trim()}]";
yield break;
}
// اگه انتهای این تکه شبیه شروع نشانهی خطا بود، نگهش داریم تا با تکهی بعدی چک بشه
var safeLength = Math.Max(0, combined.Length - (ErrMarker.Length - 1));
var toKeep = combined.Length - safeLength;
if (toKeep > 0)
{
carry = combined.Substring(safeLength);
combined = combined.Substring(0, safeLength);
}
if (combined.Length > 0)
{
yield return combined;
}
}
if (!string.IsNullOrEmpty(carry))
{
yield return carry;
}
}
/// <summary>
/// نسخهی غیر استریم (برای جاهایی که فقط متن نهایی کامل لازمه)
/// </summary>
public static async Task<string> GetFromAiAsync(
List<AiMessage> messages,
bool think = false,
bool search = false,
bool hasImage = false,
string? imageBase64 = null,
string? imageMimeType = null)
{
var sb = new StringBuilder();
await foreach (var piece in GetFromAiStreamAsync(messages, think, search, hasImage, imageBase64, imageMimeType))
{
sb.Append(piece);
}
return sb.ToString();
}
// نسخهی قدیمی: برای سازگاری با هر جای دیگهی پروژه که هنوز با یک متن تخت صداش میزنه
public static Task<string> GetFromAiAsync(string text)
{
return GetFromAiAsync(new List<AiMessage>
{
new AiMessage { Role = "user", Text = text }
});
}
/// <summary>
/// یه عنوان کوتاه (چندکلمهای) برای چت، بر اساس اولین پیام کاربر، از خود هوش مصنوعی میگیره.
/// این یه درخواست جدا و سبک هست (غیر استریم، بدون think/search/تصویر => سریعترین مدل پیشفرض)
/// و ربطی به تاریخچهی اصلی مکالمه نداره.
/// </summary>
public static async Task<string> GetChatTitleAsync(string userMessage)
{
var prompt =
"Based on the user message below, write a short topic/title for this chat. " +
"Reply with ONLY the title itself — no quotes, no punctuation at the end, no explanation, no prefix like 'Title:'. " +
"Maximum 6 words. Use the same language as the user's message.\n\n" +
"User message:\n" + userMessage;
var messages = new List<AiMessage>
{
new AiMessage { Role = "user", Text = prompt }
};
var result = await GetFromAiAsync(messages);
if (string.IsNullOrWhiteSpace(result))
{
return "";
}
result = result.Trim();
// حذف پیشوندهای احتمالی مثل "Title:" یا "عنوان:"
result = Regex.Replace(result, @"^(title|عنوان)\s[::]\s", "", RegexOptions.IgnoreCase);
// حذف نقلقولهای احتمالی دور عنوان
result = result.Trim('"', '\'', '«', '»', '“', '”').Trim();
// احتیاطاً یه سقف طول هم میذاریم، برای وقتی مدل به محدودیت کلمهای پایبند نبود
if (result.Length > 80)
{
result = result.Substring(0, 80).TrimEnd();
}
return result;
}
}
}
این کد کامل شرح بده