diff --git a/src/JobsMedical.Web/Pages/Admin/Settings.cshtml b/src/JobsMedical.Web/Pages/Admin/Settings.cshtml index 9c48297..8486b89 100644 --- a/src/JobsMedical.Web/Pages/Admin/Settings.cshtml +++ b/src/JobsMedical.Web/Pages/Admin/Settings.cshtml @@ -15,6 +15,8 @@ @if (Model.Saved is not null) {
✓ @Model.Saved
} @if (Model.DemoMsg is not null) {
@Model.DemoMsg
} @if (Model.SmsTest is not null) {
@Model.SmsTest
} + @if (Model.ProxyTest is not null) {
@Model.ProxyTest
} + @if (Model.AiTest is not null) {
@Model.AiTest
}
@@ -78,6 +80,8 @@ ارسال درخواست هوش مصنوعی از طریق پروکسی برای دسترسی به سرویس‌هایی مثل OpenAI از داخل ایران؛ از همان آدرس پروکسی تب «منابع جمع‌آوری» استفاده می‌کند. + +

یک آگهی نمونه را به مدل می‌فرستد و تصمیم/استخراج آن را نشان می‌دهد. (ابتدا کلید و آدرس را ذخیره کن.)

@@ -144,6 +148,8 @@

یک کلاینت Xray/V2Ray کانفیگ vmess/vless/trojan تو را به یک پروکسی محلی تبدیل می‌کند (socks5:// یا socks4:// یا http://). هر منبع جداگانه با تیکِ «از پروکسی استفاده شود» تعیین می‌کند که از این پروکسی عبور کند یا نه.

+ +

از طریق پروکسی به یک سایت فیلترشده وصل می‌شود؛ موفقیت یعنی تونل برقرار است. (ابتدا آدرس را ذخیره کن.)

diff --git a/src/JobsMedical.Web/Pages/Admin/Settings.cshtml.cs b/src/JobsMedical.Web/Pages/Admin/Settings.cshtml.cs index b5e5276..0db3091 100644 --- a/src/JobsMedical.Web/Pages/Admin/Settings.cshtml.cs +++ b/src/JobsMedical.Web/Pages/Admin/Settings.cshtml.cs @@ -14,11 +14,16 @@ public class SettingsModel : PageModel private readonly SettingsService _settings; private readonly ISmsSender _sms; private readonly AppDbContext _db; - public SettingsModel(SettingsService settings, ISmsSender sms, AppDbContext db) + private readonly ScrapeHttpClients _clients; + private readonly IAiAuditor _ai; + public SettingsModel(SettingsService settings, ISmsSender sms, AppDbContext db, + ScrapeHttpClients clients, IAiAuditor ai) { _settings = settings; _sms = sms; _db = db; + _clients = clients; + _ai = ai; } [BindProperty] public IngestionMode Mode { get; set; } @@ -65,6 +70,8 @@ public class SettingsModel : PageModel [TempData] public string? Saved { get; set; } [TempData] public string? SmsTest { get; set; } [TempData] public string? DemoMsg { get; set; } + [TempData] public string? ProxyTest { get; set; } + [TempData] public string? AiTest { get; set; } public async Task OnGetAsync() { @@ -172,6 +179,50 @@ public class SettingsModel : PageModel return RedirectToPage(); } + /// Check the VPN/proxy is connected by reaching a normally-blocked site through it. + public async Task OnPostTestProxyAsync() + { + var s = await _settings.GetAsync(); + if (string.IsNullOrWhiteSpace(s.IngestProxyUrl)) + { ProxyTest = "ابتدا آدرس پروکسی را وارد و ذخیره کن."; return RedirectToPage(); } + + var client = _clients.For(s, useProxy: true); + var sw = System.Diagnostics.Stopwatch.StartNew(); + try + { + // api.telegram.org is filtered in Iran — a reply means the tunnel reaches the open internet. + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); + using var resp = await client.GetAsync("https://api.telegram.org", + HttpCompletionOption.ResponseHeadersRead, cts.Token); + sw.Stop(); + ProxyTest = $"✅ پروکسی وصل است — به اینترنت آزاد دسترسی دارد (HTTP {(int)resp.StatusCode}، {sw.ElapsedMilliseconds} میلی‌ثانیه)."; + } + catch (Exception ex) + { + ProxyTest = "❌ اتصال از طریق پروکسی ناموفق بود. مطمئن شو سرویس Xray اجراست و کانفیگ معتبر است. خطا: " + ex.Message; + } + return RedirectToPage(); + } + + /// Send a sample post to the AI endpoint and show the verdict (validates key/endpoint/proxy). + public async Task OnPostTestAiAsync() + { + var s = await _settings.GetAsync(); + if (!s.AiEnabled || string.IsNullOrWhiteSpace(s.AiEndpoint)) + { AiTest = "ابتدا «فعال‌سازی هوش مصنوعی» را بزن و آدرس/کلید را ذخیره کن."; return RedirectToPage(); } + + const string sample = "استخدام پرستار خانم برای بخش اورژانس بیمارستان میلاد تهران، شیفت شب، حقوق توافقی، تماس ۰۹۱۲۱۲۳۴۵۶۷"; + try + { + var r = await _ai.AuditAsync(sample, s); + AiTest = r is null + ? "❌ پاسخی از هوش مصنوعی دریافت نشد. کلید/آدرس و (در صورت نیاز) تیک «از طریق پروکسی» را بررسی کن." + : $"✅ هوش مصنوعی پاسخ داد — تصمیم: {r.Decision} | اطمینان: {r.Confidence}٪ | نقش: {r.Data?.Role} | شهر: {r.Data?.City} | شیفت: {r.Data?.ShiftType}"; + } + catch (Exception ex) { AiTest = "❌ خطا در تماس با هوش مصنوعی: " + ex.Message; } + return RedirectToPage(); + } + public async Task OnPostTestSmsAsync() { var s = await _settings.GetAsync(); diff --git a/src/JobsMedical.Web/Services/Scraping/AiAuditor.cs b/src/JobsMedical.Web/Services/Scraping/AiAuditor.cs index 9d306e4..41c271e 100644 --- a/src/JobsMedical.Web/Services/Scraping/AiAuditor.cs +++ b/src/JobsMedical.Web/Services/Scraping/AiAuditor.cs @@ -94,10 +94,12 @@ public class OpenAiCompatibleAuditor : IAiAuditor using var doc = JsonDocument.Parse(json); var r = doc.RootElement; + // Guard on ValueKind == Number first — TryGetInt32/64 THROW on null/string values + // (the model often returns payAmount/sharePercent as null), which would fail the whole parse. string? S(string k) => r.TryGetProperty(k, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null; - int I(string k, int d) => r.TryGetProperty(k, out var v) && v.TryGetInt32(out var n) ? n : d; - long? L(string k) => r.TryGetProperty(k, out var v) && v.TryGetInt64(out var n) ? n : null; - int? NI(string k) => r.TryGetProperty(k, out var v) && v.TryGetInt32(out var n) ? n : null; + int I(string k, int d) => r.TryGetProperty(k, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n) ? n : d; + long? L(string k) => r.TryGetProperty(k, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt64(out var n) ? n : null; + int? NI(string k) => r.TryGetProperty(k, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out var n) ? n : null; var decision = (S("decision") ?? "review").ToLowerInvariant(); var data = new AiStructured(S("kind"), S("role"), S("city"), S("district"), S("shiftType"),