Jahadona commited on
Commit
f6b32fe
·
verified ·
1 Parent(s): 5a87312

Update script.js

Browse files
Files changed (1) hide show
  1. script.js +67 -53
script.js CHANGED
@@ -1,9 +1,9 @@
1
  // این فایل script.js نسخه نهایی شده بر اساس کد اصلی شماست.
2
- // تغییرات لازم برای کار در Hugging Face Space جدید و مدل heydariAI/persian-embeddings اعمال شده است.
3
- // شامل تمام قابلیت های اضافه شده و رفع اشکالات ID های شناسایی شده.
4
 
5
  // ****** تعریف متغیرهای عناصر HTML در بالاترین اسکوپ ******
6
- // این متغیرها در بلوک DOMContentLoaded مقداردهی اولیه می شوند.
7
  let searchButton;
8
  let userQuestionInput; // <--- مطابق با ID در index.html شما
9
  let searchResultsContainer; // <--- مطابق با ID در index.html شما
@@ -21,16 +21,13 @@ let similarityThresholdInput; // <--- مطابق با ID در index.html شما
21
 
22
 
23
  // ****** تعریف URL سرور پایتون برای دریافت Embedding سوال ******
24
- // آدرس لوکال برای ارتباط با Backend در همان کانتینر Docker روی پورت 7860
25
- // این آدرس ثابت است و نیازی به جایگزینی با آدرس عمومی Space ندارد.
26
- const EMBEDDING_SERVER_URL = '/get_embedding'; // <--- تغییر به آدرس نسبی
27
 
28
 
29
  // ****** نگاشت نام فایل JSON به نام کامل کتاب (برای نمایش در نتایج و فیلتر) ******
30
  // این map اطلاعات نمایشی کتاب را بر اساس value چک باکس (نام فایل JSON) فراهم می کند.
31
  // مطمئن شوید که value چک باکس ها در index.html شما با کلیدهای این map مطابقت دارد.
32
- // همچنین book_title در فایل های JSON شما باید با مقادیر این map مطابقت داشته باشد
33
- // زیرا فیلتر بر اساس book_title در JSON انجام می شود.
34
  const bookInfo = {
35
  // 'نام_فایل_json_در_value_چک_باکس': 'نام کامل نمایشی کتاب',
36
  'jabe_siah.json': 'جعبه سیاه (منتخب خاطرات اسدالله علم)', // مثال: value چک باکس : نام نمایشی کتاب
@@ -97,7 +94,7 @@ function setButtonEnabled(enabled) {
97
  }
98
 
99
  // ****** تابع برای بررسی وضعیت و فعال/غیرفعال کردن دکمه جستجو ******
100
- // دکمه فقط زمانی فعال می شود که داده بارگذاری شده و کادر سوال خالی نیست و آستانه معتبر است.
101
  function checkAndEnableSearchButton() {
102
  const isDataLoaded = memoirsWithEmbeddings.length > 0;
103
  // اطمینان از وجود userQuestionInput قبل از دسترسی به value
@@ -211,9 +208,10 @@ async function updateSelectedBooksData() {
211
 
212
  if (Array.isArray(data)) {
213
  // فیلتر کردن آیتم هایی که بردار embedding معتبر دارند و افزودن اطلاعات کتاب به آن ها
214
- const memoirsWithBookInfo = data.filter(item => item && typeof item === 'object' && item.embedding && Array.isArray(item.embedding) && item.embedding.length > 0) // اضافه کردن چک item !== null و item از نوع object باشد
 
215
  .map(item => ({
216
- ...item, // کپی کردن تمام پراپرتی های موجود (passage, reference, embedding, passage_original, etc.)
217
  book_title: bookDisplayName, // افزودن نام نمایشی کتاب
218
  book_file: filename // افزودن نام فایل اصلی کتاب
219
  }));
@@ -241,7 +239,7 @@ async function updateSelectedBooksData() {
241
  updateSelectionError("هیچ خاطره‌ای با بردار معتبر از کتاب‌های انتخاب شده یافت نشد. فرمت فایل‌های JSON و وجود فیلد embedding را بررسی کنید.");
242
  } else {
243
  console.log(`Successfully loaded data from ${loadedBooksCount} book(s). Total valid passages loaded: ${totalPassagesLoaded}`); // لاگ برای توسعه
244
- updateStatus(`داده‌ها از ${loadedBooksCount} کتاب با موفقیت بارگذاری شد. مجموع خاطرات قابل جستجو: ${totalPassagesLoaded}. آماده جستجو هستید.`); // پیام برای کاربر
245
  }
246
 
247
  checkAndEnableSearchButton(); // بررسی و فعال کردن دکمه پس از بارگذاری داده
@@ -249,7 +247,7 @@ async function updateSelectedBooksData() {
249
 
250
  } catch (error) {
251
  console.error("Error loading selected books data:", error); // لاگ برای توسعه
252
- updateStatus("خطا در بارگذاری داده‌ها.", true); // پیام برای کاربر
253
  // پیام خطای برای کاربر شامل جزئیات بیشتر از علت خطا
254
  updateSelectionError(`خطا در بارگذاری داده‌ها: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر.`);
255
  memoirsWithEmbeddings = []; // اطمینان از خالی بودن داده در صورت خطا
@@ -259,8 +257,7 @@ async function updateSelectedBooksData() {
259
  }
260
  } finally {
261
  // در نهایت، چه بارگذاری موفقیت آمیز باشد چه با خطا مواجه شود، دکمه جستجو وضعیت خود را بررسی می کند.
262
- // checkAndEnableSearchButton() این کار را انجام می دهد.
263
- // checkAndEnableSearchButton(); // در اینجا نیازی نیست، چون در هر دو شاخه موفقیت و خطا صدا زده می شود.
264
  }
265
  }
266
 
@@ -303,6 +300,8 @@ function cosineSimilarity(vecA, vecB) {
303
 
304
  // تابع کمکی برای حذف بخش کلمات کلیدی از متن Passage
305
  // این تابع بر اساس فرمتی که در generate_embeddings.py برای ترکیب متن و کلیدواژه ها استفاده شده، کار می کند.
 
 
306
  function cleanPassageTextForDisplay(passage) {
307
  if (!passage || typeof passage !== 'string') {
308
  console.warn("cleanPassageTextForDisplay received invalid input:", passage);
@@ -365,7 +364,7 @@ async function searchMemoirs() {
365
 
366
 
367
  try {
368
- console.log("Requesting query embedding from Python server..."); // لاگ برای توسعه
369
 
370
  // ارسال درخواست به سرور Backend برای دریافت بردار سوال
371
  const serverResponse = await fetch(EMBEDDING_SERVER_URL, {
@@ -379,10 +378,10 @@ async function searchMemoirs() {
379
  // بررسی موفقیت آمیز بودن پاسخ سرور (کد وضعیت 2xx)
380
  if (!serverResponse.ok) {
381
  const errorBody = await serverResponse.text(); // بخوان به صورت متن برای اطلاعات بیشتر
382
- console.error(`Server responded with status ${serverResponse.status}:`, errorBody); // لاگ خطا برای توسعه
383
 
384
  // تلاش برای تجزیه پاسخ خطا به صورت JSON اگر سرور خطای JSON برگردانده باشد
385
- let serverErrorMessage = `خطا از سرور (${serverResponse.status}).`;
386
  try {
387
  const errorJson = JSON.parse(errorBody);
388
  if (errorJson.error) {
@@ -390,12 +389,11 @@ async function searchMemoirs() {
390
  }
391
  } catch (e) {
392
  // اگر پاسخ خطا JSON نبود، متن خام را اضافه کن
393
- serverErrorMessage += ` پاسخ خام: ${errorBody.substring(0, Math.min(errorBody.length, 100))}...`; // نمایش بخشی از پاسخ خام (حداکثر 100 کاراکتر)
394
  }
395
 
396
  updateStatus("جستجو با خطا مواجه شد.", true); // پیام اصلی برای کاربر (رنگ قرمز)
397
  updateSelectionError(serverErrorMessage + " جزئیات بیشتر در کنسول مرورگر."); // نمایش جزئیات خطا در بخش خطای انتخاب
398
- // نیازی به throw new Error نیست چون در finally دکمه فعال می شود و پیام ها نمایش داده شده اند.
399
  return; // خروج از تابع searchMemoirs پس از خطا
400
 
401
  }
@@ -406,13 +404,13 @@ async function searchMemoirs() {
406
 
407
  // بررسی اینکه بردار embedding معتبر و غیر خالی است
408
  if (!queryEmbeddingArray || !Array.isArray(queryEmbeddingArray) || queryEmbeddingArray.length === 0) {
409
- console.error("Server returned an invalid or empty embedding:", serverData); // لاگ خطا برای توسعه
410
  updateStatus("جستجو با خطا مواجه شد.", true); // پیام اصلی برای کاربر
411
- updateSelectionError("سرور بردار جستجو را به درستی برنگرداند. جزئیات در کنسول مرورگر."); // نمایش جزئیات خطا در بخش خطای انتخاب
412
  return; // خروج از تابع searchMemoirs
413
  }
414
 
415
- console.log("Query embedding received from server successfully."); // لاگ برای توسعه
416
  console.log(`Query embedding dimensions: ${queryEmbeddingArray.length}`); // لاگ برای توسعه
417
  console.log("Calculating similarities in browser..."); // لاگ برای توسعه
418
 
@@ -475,6 +473,9 @@ async function searchMemoirs() {
475
  // برش لیست نتایج برای نمایش فقط تعداد مورد نظر
476
  const topResults = filteredResults.slice(0, finalResultsPerPage);
477
  console.log(`Displaying top ${topResults.length} results from filtered list based on user selection (${finalResultsPerPage} per page setting).`);
 
 
 
478
  // ********************************************
479
 
480
 
@@ -508,7 +509,8 @@ async function searchMemoirs() {
508
  similarityElement.classList.add('result-similarity');
509
  // نمایش امتیاز با 4 رقم اعشار
510
  similarityElement.textContent = `شباهت: ${result.similarity !== undefined ? result.similarity.toFixed(4) : 'N/A'}`;
511
- resultItem.appendChild(similarityElement); // اضافه کردن به آیتم نتیجه
 
512
  // ******************************
513
 
514
 
@@ -517,7 +519,7 @@ async function searchMemoirs() {
517
  const bookTitleElement = document.createElement('p');
518
  bookTitleElement.classList.add('result-book-title');
519
  bookTitleElement.textContent = `از کتاب: ${result.book_title || 'نامشخص'}`;
520
- resultItem.appendChild(bookTitleElement); // اضافه کردن به آیتم نتیجه
521
 
522
 
523
  // نمایش مرجع خاطره
@@ -528,12 +530,14 @@ async function searchMemoirs() {
528
  resultItem.appendChild(referenceElement); // اضافه کردن به آیتم نتیجه
529
 
530
 
531
- // نمایش متن خاطره (با حذف کلمات کلیدی)
532
  // استفاده از p مطابق با ساختار HTML و کلاس CSS شما
533
  const passageElement = document.createElement('p');
534
  passageElement.classList.add('result-passage');
535
- passageElement.textContent = result.passage_original || 'متن خاطره موجود نیست.'; // <-- نمایش passage_original
536
- resultItem.appendChild(passageElement); // اضافه کردن به آیتم نتیجه
 
 
537
 
538
 
539
  // ****** اضافه کردن دکمه کپی ******
@@ -561,9 +565,7 @@ async function searchMemoirs() {
561
 
562
 
563
  // Event listener برای دکمه کپی
564
- // این Listener در بلوک DOMContentLoaded به صورت Delegation اضافه می شود.
565
- // کد مربوط به Listener در انتهای بلوک DOMContentLoaded آورده شده است.
566
- // در اینجا فقط دکمه را به آیتم نتیجه اضافه می کنیم.
567
  resultItem.appendChild(copyButton); // دکمه کپی در انتها
568
 
569
  // **************************************************************************
@@ -575,10 +577,10 @@ async function searchMemoirs() {
575
  searchResultsContainer.appendChild(resultsList); // اضافه کردن لیست نتایج به کانتینر اصلی
576
  console.log("DOM updated with results.");
577
 
578
- // لاگ کردن نتایج برتر نمایش داده شده برای عیب یابی
579
  console.log(`Top ${topResults.length} results displayed (reference, book, similarity, passage start):`);
580
  topResults.forEach(result => {
581
- console.log(` Book: ${result.book_title || 'Unknown'}, Ref: ${result.reference || 'N/A'}, Sim: ${result.similarity !== undefined ? result.similarity.toFixed(4) : 'N/A'}, Passage: "${result.passage ? result.passage.substring(0, Math.min(result.passage.length, 50)).replace(/\n/g, ' ') + '...' : 'N/A'}"`); // لاگ جزئیات بیشتر و جایگزینی خط جدید
582
  });
583
  }
584
 
@@ -598,10 +600,10 @@ async function searchMemoirs() {
598
 
599
 
600
  } catch (error) {
601
- // مدیریت خطا هنگام درخواست سرور یا پردازش پاسخ
602
  console.error("Error during search:", error); // لاگ
603
  if (searchResultsContainer) { // استفاده از نام متغیر صحیح
604
- searchResultsContainer.innerHTML = `<p style="color: red;">هنگام جستجو خطایی رخ داد: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر موجود است.</p>`;
605
  }
606
  updateStatus("جستجو با خطا مواجه شد.", true); // پیام خطا (رنگ قرمز)
607
  updateSelectionError(`هنگام جستجو خطایی رخ داد: ${error.message || 'خطای نامشخص'}.`); // نمایش خطای اصلی در بخش خطای انتخاب
@@ -620,17 +622,17 @@ document.addEventListener('DOMContentLoaded', () => {
620
  // ****** دریافت رفرنس المان های HTML (مطابق با ID ها و کلاس ها در index.html شما) ******
621
  // این رفرنس ها متغیرهای سراسری تعریف شده در بالای فایل هستند.
622
  searchButton = document.getElementById('searchButton');
623
- userQuestionInput = document.getElementById('userQuestion');
624
- searchResultsContainer = document.getElementById('searchResults');
625
- loadingStatusElement = document.getElementById('loadingStatus');
626
- selectionErrorElement = document.getElementById('selectionError'); // مطابق با ID در HTML شما
627
 
628
- selectAllCheckbox = document.getElementById('select_all_books'); // مطابق با ID در HTML شما
629
  // getElementsByClassName برمی گرداند HTMLCollection زنده.
630
- bookCheckboxes = document.getElementsByClassName('book-checkbox'); // مطابق با class در HTML شما
631
 
632
- resultsPerPageSelect = document.getElementById('resultsPerPage'); // مطابق با ID در HTML شما
633
- similarityThresholdInput = document.getElementById('similarityThresholdInput'); // مطابق با ID در HTML شما
634
  // **********************************************************************************
635
 
636
 
@@ -662,18 +664,28 @@ document.addEventListener('DOMContentLoaded', () => {
662
  console.log("Search input keypress listener added.");
663
 
664
 
 
 
 
 
 
 
 
 
 
 
665
  // Listener برای تغییر مقدار ورودی آستانه شباهت: به‌روزرسانی وضعیت دکمه (فعال/غیرفعال شدن)
666
  // مقدار آستانه شباهت مستقیماً در تابع searchMemoirs از این input خوانده می شود.
667
  similarityThresholdInput.addEventListener('input', () => {
668
- console.log("Similarity threshold input value changed.");
669
- checkAndEnableSearchButton(); // بررسی وضعیت دکمه پس از تغییر مقدار
670
  });
671
  console.log("Similarity threshold input listener added.");
672
 
673
  // Listener برای تغییر انتخاب در نتایج در صفحه (resultsPerPageSelect)
674
  // مقدار نتایج در صفحه مستقیماً در تابع searchMemoirs از این select خوانده می شود.
675
  resultsPerPageSelect.addEventListener('change', () => {
676
- console.log("Results per page setting changed.");
677
  // نیازی به checkAndEnableSearchButton نیست چون تغییر این مقدار به تنهایی دکمه را فعال/غیرفعال نمی کند.
678
  });
679
  console.log("Results per page select listener added.");
@@ -695,7 +707,7 @@ document.addEventListener('DOMContentLoaded', () => {
695
  });
696
  console.log("'Select All' checkbox change listener added.");
697
  } else {
698
- console.error("'Select All' checkbox element with ID 'select_all_books' not found.");
699
  // اگر این المان پیدا نشد، قابلیت "انتخاب همه" کار نخواهد کرد.
700
  }
701
 
@@ -714,7 +726,7 @@ document.addEventListener('DOMContentLoaded', () => {
714
  });
715
  console.log("Individual book checkboxes change listeners added.");
716
  } else {
717
- console.warn("No book checkboxes found with class 'book-checkbox'. Book selection filtering will not work as expected.");
718
  // اگر المان های چک باکس تکی پیدا نشدند، فیلترینگ بر اساس کتاب انتخاب شده کار نخواهد کرد.
719
  // در این حالت، updateSelectedBooksData با لیست خالی selectedBookFiles اجرا خواهد شد.
720
  }
@@ -733,15 +745,16 @@ document.addEventListener('DOMContentLoaded', () => {
733
  if (resultItemElement) {
734
  // متن کپی شامل متن خاطره، مرجع، نام کتاب و شباهت است.
735
  // این اطلاعات از متن نمایش داده شده در المان های نتیجه استخراج می شود.
736
- const passageText = resultItemElement.querySelector('.result-passage')?.textContent || '';
737
  // استفاده از ? در selector برای جلوگیری از خطا اگر المان پیدا نشد.
 
738
  // جایگزینی 'مرجع:' و trim برای تمیز کردن متن استخراج شده.
739
  const referenceText = resultItemElement.querySelector('.result-reference')?.textContent.replace('مرجع:', '').trim() || 'نامشخص';
740
  const bookTitleText = resultItemElement.querySelector('.result-book-title')?.textContent.replace('از کتاب:', '').trim() || 'نامشخص';
741
- const similarityText = resultItemElement.querySelector('.result-similarity')?.textContent.replace('شباهت:', '').trim() || 'N/A';
742
 
743
  // ساخت متنی که باید کپی شود با فرمت دلخواه
744
- const textToCopy = `خاطره:\n${passageText}\n\nمرجع: ${referenceText}\nاز کتاب: ${bookTitleText}\nشباهت: ${similarityText}`;
 
745
 
746
  // کپی کردن متن به کلیپ بورد
747
  navigator.clipboard.writeText(textToCopy)
@@ -761,7 +774,7 @@ document.addEventListener('DOMContentLoaded', () => {
761
  }, 2000);
762
  });
763
  } else {
764
- console.warn("Result item parent not found for copy button."); // لاگ
765
  }
766
  }
767
  });
@@ -801,4 +814,5 @@ document.addEventListener('DOMContentLoaded', () => {
801
  }
802
  // نیازی به return نیست چون کد بعد از این بلوک اجرا نمی شود (به دلیل عدم وجود المان های ضروری)
803
  }
804
- });
 
 
1
  // این فایل script.js نسخه نهایی شده بر اساس کد اصلی شماست.
2
+ // شامل تمام اصلاحات لازم برای کار در Hugging Face Space جدید با مدل heydariAI/persian-embeddings.
3
+ // تمام قابلیت های اضافه شده و رفع اشکالات شناسایی شده در فرآیند عیب یابی در آن لحاظ شده است.
4
 
5
  // ****** تعریف متغیرهای عناصر HTML در بالاترین اسکوپ ******
6
+ // این متغیرها در بلوک DOMContentLoaded پس از بارگذاری صفحه مقداردهی اولیه می شوند.
7
  let searchButton;
8
  let userQuestionInput; // <--- مطابق با ID در index.html شما
9
  let searchResultsContainer; // <--- مطابق با ID در index.html شما
 
21
 
22
 
23
  // ****** تعریف URL سرور پایتون برای دریافت Embedding سوال ******
24
+ // آدرس نسبی برای ارتباط با Backend در همان سرور Flask که Frontend را سرویس می دهد.
25
+ const EMBEDDING_SERVER_URL = '/get_embedding'; // <--- آدرس نسبی برای Space مشترک
 
26
 
27
 
28
  // ****** نگاشت نام فایل JSON به نام کامل کتاب (برای نمایش در نتایج و فیلتر) ******
29
  // این map اطلاعات نمایشی کتاب را بر اساس value چک باکس (نام فایل JSON) فراهم می کند.
30
  // مطمئن شوید که value چک باکس ها در index.html شما با کلیدهای این map مطابقت دارد.
 
 
31
  const bookInfo = {
32
  // 'نام_فایل_json_در_value_چک_باکس': 'نام کامل نمایشی کتاب',
33
  'jabe_siah.json': 'جعبه سیاه (منتخب خاطرات اسدالله علم)', // مثال: value چک باکس : نام نمایشی کتاب
 
94
  }
95
 
96
  // ****** تابع برای بررسی وضعیت و فعال/غیرفعال کردن دکمه جستجو ******
97
+ // دکمه فقط زمانی فعال می شود که داده بارگذاری شده، کادر سوال خالی نیست و آستانه معتبر است.
98
  function checkAndEnableSearchButton() {
99
  const isDataLoaded = memoirsWithEmbeddings.length > 0;
100
  // اطمینان از وجود userQuestionInput قبل از دسترسی به value
 
208
 
209
  if (Array.isArray(data)) {
210
  // فیلتر کردن آیتم هایی که بردار embedding معتبر دارند و افزودن اطلاعات کتاب به آن ها
211
+ // مطمئن می شویم که آیتم یک آبجکت معتبر است و فیلد embedding را دارد
212
+ const memoirsWithBookInfo = data.filter(item => item && typeof item === 'object' && item.embedding && Array.isArray(item.embedding) && item.embedding.length > 0)
213
  .map(item => ({
214
+ ...item, // کپی کردن تمام پراپرتی های موجود (passage_original, passage_combined, reference, embedding, etc.)
215
  book_title: bookDisplayName, // افزودن نام نمایشی کتاب
216
  book_file: filename // افزودن نام فایل اصلی کتاب
217
  }));
 
239
  updateSelectionError("هیچ خاطره‌ای با بردار معتبر از کتاب‌های انتخاب شده یافت نشد. فرمت فایل‌های JSON و وجود فیلد embedding را بررسی کنید.");
240
  } else {
241
  console.log(`Successfully loaded data from ${loadedBooksCount} book(s). Total valid passages loaded: ${totalPassagesLoaded}`); // لاگ برای توسعه
242
+ updateStatus(`داده‌ها از ${loadedBooksCount} کتاب با موفقیت بارگذاری شد. مجموع خاطرات قابل جستجو: ${totalPassagesLoaded}. آماده جستجو هستید.`); // پیام برای کار
243
  }
244
 
245
  checkAndEnableSearchButton(); // بررسی و فعال کردن دکمه پس از بارگذاری داده
 
247
 
248
  } catch (error) {
249
  console.error("Error loading selected books data:", error); // لاگ برای توسعه
250
+ updateStatus("خطا در بارگذاری داده‌ها.", true); // پیام برای کار
251
  // پیام خطای برای کاربر شامل جزئیات بیشتر از علت خطا
252
  updateSelectionError(`خطا در بارگذاری داده‌ها: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر.`);
253
  memoirsWithEmbeddings = []; // اطمینان از خالی بودن داده در صورت خطا
 
257
  }
258
  } finally {
259
  // در نهایت، چه بارگذاری موفقیت آمیز باشد چه با خطا مواجه شود، دکمه جستجو وضعیت خود را بررسی می کند.
260
+ // checkAndEnableSearchButton(); // این فراخوانی در پایان هر دو شاخه try/catch در بالا نیز وجود دارد.
 
261
  }
262
  }
263
 
 
300
 
301
  // تابع کمکی برای حذف بخش کلمات کلیدی از متن Passage
302
  // این تابع بر اساس فرمتی که در generate_embeddings.py برای ترکیب متن و کلیدواژه ها استفاده شده، کار می کند.
303
+ // با توجه به استفاده مستقیم از passage_original، این تابع ممکن است دیگر به صورت مستقیم برای نمایش متن اصلی استفاده نشود،
304
+ // اما می تواند برای پردازش passage_combined اگر کاربر بخواهد استفاده شود.
305
  function cleanPassageTextForDisplay(passage) {
306
  if (!passage || typeof passage !== 'string') {
307
  console.warn("cleanPassageTextForDisplay received invalid input:", passage);
 
364
 
365
 
366
  try {
367
+ console.log("Requesting query embedding from Backend..."); // لاگ برای توسعه
368
 
369
  // ارسال درخواست به سرور Backend برای دریافت بردار سوال
370
  const serverResponse = await fetch(EMBEDDING_SERVER_URL, {
 
378
  // بررسی موفقیت آمیز بودن پاسخ سرور (کد وضعیت 2xx)
379
  if (!serverResponse.ok) {
380
  const errorBody = await serverResponse.text(); // بخوان به صورت متن برای اطلاعات بیشتر
381
+ console.error(`Backend responded with status ${serverResponse.status}:`, errorBody); // لاگ خطا برای توسعه
382
 
383
  // تلاش برای تجزیه پاسخ خطا به صورت JSON اگر سرور خطای JSON برگردانده باشد
384
+ let serverErrorMessage = `خطا از Backend (${serverResponse.status}).`;
385
  try {
386
  const errorJson = JSON.parse(errorBody);
387
  if (errorJson.error) {
 
389
  }
390
  } catch (e) {
391
  // اگر پاسخ خطا JSON نبود، متن خام را اضافه کن
392
+ serverErrorMessage += ` پاسخ خام: ${errorBody.substring(0, Math.min(errorBody.length, 100))}...`; // نمایش بخشی از پاسخ خام (حداثر 100 کاراکتر)
393
  }
394
 
395
  updateStatus("جستجو با خطا مواجه شد.", true); // پیام اصلی برای کاربر (رنگ قرمز)
396
  updateSelectionError(serverErrorMessage + " جزئیات بیشتر در کنسول مرورگر."); // نمایش جزئیات خطا در بخش خطای انتخاب
 
397
  return; // خروج از تابع searchMemoirs پس از خطا
398
 
399
  }
 
404
 
405
  // بررسی اینکه بردار embedding معتبر و غیر خالی است
406
  if (!queryEmbeddingArray || !Array.isArray(queryEmbeddingArray) || queryEmbeddingArray.length === 0) {
407
+ console.error("Backend returned an invalid or empty embedding:", serverData); // لاگ خطا برای توسعه
408
  updateStatus("جستجو با خطا مواجه شد.", true); // پیام اصلی برای کاربر
409
+ updateSelectionError("Backend بردار جستجو را به درستی برنگرداند. جزئیات در کنسول مرورگر."); // نمایش جزئیات خطا در بخش خطای انتخاب
410
  return; // خروج از تابع searchMemoirs
411
  }
412
 
413
+ console.log("Query embedding received from Backend successfully."); // لاگ برای توسعه
414
  console.log(`Query embedding dimensions: ${queryEmbeddingArray.length}`); // لاگ برای توسعه
415
  console.log("Calculating similarities in browser..."); // لاگ برای توسعه
416
 
 
473
  // برش لیست نتایج برای نمایش فقط تعداد مورد نظر
474
  const topResults = filteredResults.slice(0, finalResultsPerPage);
475
  console.log(`Displaying top ${topResults.length} results from filtered list based on user selection (${finalResultsPerPage} per page setting).`);
476
+
477
+ // لاگ کردن نتایج برتر نمایش داده شده برای عیب یابی (اختیاری، می توانید حذف کنید)
478
+ console.log("Top results data before display:", topResults); // <-- لاگ برای بررسی داده های نتایج
479
  // ********************************************
480
 
481
 
 
509
  similarityElement.classList.add('result-similarity');
510
  // نمایش امتیاز با 4 رقم اعشار
511
  similarityElement.textContent = `شباهت: ${result.similarity !== undefined ? result.similarity.toFixed(4) : 'N/A'}`;
512
+ // اضافه کردن به آیتم نتیجه (ترتیب مهم است برای فلوت float: right)
513
+ resultItem.appendChild(similarityElement);
514
  // ******************************
515
 
516
 
 
519
  const bookTitleElement = document.createElement('p');
520
  bookTitleElement.classList.add('result-book-title');
521
  bookTitleElement.textContent = `از کتاب: ${result.book_title || 'نامشخص'}`;
522
+ resultItem.appendChild(bookTitleElement); // اضافه کردن به آیتم نتیجه
523
 
524
 
525
  // نمایش مرجع خاطره
 
530
  resultItem.appendChild(referenceElement); // اضافه کردن به آیتم نتیجه
531
 
532
 
533
+ // ****** نمایش متن اصلی خاطره (استفاده از passage_original) ******
534
  // استفاده از p مطابق با ساختار HTML و کلاس CSS شما
535
  const passageElement = document.createElement('p');
536
  passageElement.classList.add('result-passage');
537
+ // مستقیماً از فیلد passage_original استفاده می کنیم
538
+ passageElement.textContent = result.passage_original || 'متن خاطره موجود نیست.';
539
+ resultItem.appendChild(passageElement); // اضافه کردن به آیتم نتیجه
540
+ // ***************************************************************
541
 
542
 
543
  // ****** اضافه کردن دکمه کپی ******
 
565
 
566
 
567
  // Event listener برای دکمه کپی
568
+ // این Listener در بلوک DOMContentLoaded به صورت Delegation اضافه شده و در اینجا فقط دکمه به DOM اضافه می شود.
 
 
569
  resultItem.appendChild(copyButton); // دکمه کپی در انتها
570
 
571
  // **************************************************************************
 
577
  searchResultsContainer.appendChild(resultsList); // اضافه کردن لیست نتایج به کانتینر اصلی
578
  console.log("DOM updated with results.");
579
 
580
+ // لاگ کردن نتایج برتر نمایش داده شده برای عیب یابی (اختیاری، می توانید حذف کنید)
581
  console.log(`Top ${topResults.length} results displayed (reference, book, similarity, passage start):`);
582
  topResults.forEach(result => {
583
+ console.log(` Book: ${result.book_title || 'Unknown'}, Ref: ${result.reference || 'N/A'}, Sim: ${result.similarity !== undefined ? result.similarity.toFixed(4) : 'N/A'}, Passage: "${result.passage_original ? result.passage_original.substring(0, Math.min(result.passage_original.length, 50)).replace(/\n/g, ' ') + '...' : 'N/A'}"`); // لاگ جزئیات بیشتر و جایگزینی خط جدید
584
  });
585
  }
586
 
 
600
 
601
 
602
  } catch (error) {
603
+ // مدیریت خطا هنگام درخواست به Backend یا پردازش پاسخ
604
  console.error("Error during search:", error); // لاگ
605
  if (searchResultsContainer) { // استفاده از نام متغیر صحیح
606
+ searchResultsContainer.innerHTML = `<p style=\"color: red;\">هنگام جستجو خطایی رخ داد: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر موجود است.</p>`;
607
  }
608
  updateStatus("جستجو با خطا مواجه شد.", true); // پیام خطا (رنگ قرمز)
609
  updateSelectionError(`هنگام جستجو خطایی رخ داد: ${error.message || 'خطای نامشخص'}.`); // نمایش خطای اصلی در بخش خطای انتخاب
 
622
  // ****** دریافت رفرنس المان های HTML (مطابق با ID ها و کلاس ها در index.html شما) ******
623
  // این رفرنس ها متغیرهای سراسری تعریف شده در بالای فایل هستند.
624
  searchButton = document.getElementById('searchButton');
625
+ userQuestionInput = document.getElementById('userQuestion'); // <--- مطابق با ID در index.html شما
626
+ searchResultsContainer = document.getElementById('searchResults'); // <--- مطابق با ID در index.html شما
627
+ loadingStatusElement = document.getElementById('loadingStatus'); // <--- مطابق با ID در index.html شما
628
+ selectionErrorElement = document.getElementById('selectionError'); // <--- مطابق با ID در index.html شما
629
 
630
+ selectAllCheckbox = document.getElementById('select_all_books'); // <--- مطابق با ID در index.html شما
631
  // getElementsByClassName برمی گرداند HTMLCollection زنده.
632
+ bookCheckboxes = document.getElementsByClassName('book-checkbox'); // <--- مطابق با class در index.html شما
633
 
634
+ resultsPerPageSelect = document.getElementById('resultsPerPage'); // <--- مطابق با ID در index.html شما
635
+ similarityThresholdInput = document.getElementById('similarityThresholdInput'); // <--- مطابق با ID در index.html شما
636
  // **********************************************************************************
637
 
638
 
 
664
  console.log("Search input keypress listener added.");
665
 
666
 
667
+ // ****** Listener برای تغییر محتوای کادر سوال: بررسی وضعیت دکمه جستجو ******
668
+ // این Listener باعث می شود دکمه جستجو زمانی که متن وارد می شود فعال شود.
669
+ userQuestionInput.addEventListener('input', () => {
670
+ console.log("Search input value changed, checking button state."); // لاگ برای توسعه
671
+ checkAndEnableSearchButton(); // <--- فراخوانی تابع بررسی وضعیت دکمه
672
+ });
673
+ console.log("Search input 'input' listener added.");
674
+ // *********************************************************************
675
+
676
+
677
  // Listener برای تغییر مقدار ورودی آستانه شباهت: به‌روزرسانی وضعیت دکمه (فعال/غیرفعال شدن)
678
  // مقدار آستانه شباهت مستقیماً در تابع searchMemoirs از این input خوانده می شود.
679
  similarityThresholdInput.addEventListener('input', () => {
680
+ console.log("Similarity threshold input value changed."); // لاگ
681
+ checkAndEnableSearchButton(); // بررسی وضعیت دکمه پس از تغییر مقدار (اگر مقدار نامعتبر شود دکمه غیرفعال می شود)
682
  });
683
  console.log("Similarity threshold input listener added.");
684
 
685
  // Listener برای تغییر انتخاب در نتایج در صفحه (resultsPerPageSelect)
686
  // مقدار نتایج در صفحه مستقیماً در تابع searchMemoirs از این select خوانده می شود.
687
  resultsPerPageSelect.addEventListener('change', () => {
688
+ console.log("Results per page setting changed."); // لاگ
689
  // نیازی به checkAndEnableSearchButton نیست چون تغییر این مقدار به تنهایی دکمه را فعال/غیرفعال نمی کند.
690
  });
691
  console.log("Results per page select listener added.");
 
707
  });
708
  console.log("'Select All' checkbox change listener added.");
709
  } else {
710
+ console.error("'Select All' checkbox element with ID 'select_all_books' not found."); // لاگ
711
  // اگر این المان پیدا نشد، قابلیت "انتخاب همه" کار نخواهد کرد.
712
  }
713
 
 
726
  });
727
  console.log("Individual book checkboxes change listeners added.");
728
  } else {
729
+ console.warn("No book checkboxes found with class 'book-checkbox'. Book selection filtering will not work as expected."); // لاگ
730
  // اگر المان های چک باکس تکی پیدا نشدند، فیلترینگ بر اساس کتاب انتخاب شده کار نخواهد کرد.
731
  // در این حالت، updateSelectedBooksData با لیست خالی selectedBookFiles اجرا خواهد شد.
732
  }
 
745
  if (resultItemElement) {
746
  // متن کپی شامل متن خاطره، مرجع، نام کتاب و شباهت است.
747
  // این اطلاعات از متن نمایش داده شده در المان های نتیجه استخراج می شود.
 
748
  // استفاده از ? در selector برای جلوگیری از خطا اگر المان پیدا نشد.
749
+ const passageText = resultItemElement.querySelector('.result-passage')?.textContent || '';
750
  // جایگزینی 'مرجع:' و trim برای تمیز کردن متن استخراج شده.
751
  const referenceText = resultItemElement.querySelector('.result-reference')?.textContent.replace('مرجع:', '').trim() || 'نامشخص';
752
  const bookTitleText = resultItemElement.querySelector('.result-book-title')?.textContent.replace('از کتاب:', '').trim() || 'نامشخص';
753
+ const similarityElementText = resultItemElement.querySelector('.result-similarity')?.textContent || ''; // گرفتن متن کامل عنصر شباهت (مثلا "شباهت: 0.7500")
754
 
755
  // ساخت متنی که باید کپی شود با فرمت دلخواه
756
+ const textToCopy = `خاطره:\n${passageText}\n\nمرجع: ${referenceText}\nاز کتاب: ${bookTitleText}\n${similarityElementText}`; // اضافه کردن متن کامل شباهت
757
+
758
 
759
  // کپی کردن متن به کلیپ بورد
760
  navigator.clipboard.writeText(textToCopy)
 
774
  }, 2000);
775
  });
776
  } else {
777
+ console.warn("Result item parent not found for copy button click."); // لاگ
778
  }
779
  }
780
  });
 
814
  }
815
  // نیازی به return نیست چون کد بعد از این بلوک اجرا نمی شود (به دلیل عدم وجود المان های ضروری)
816
  }
817
+ });
818
+ // پایان بلوک DOMContentLoaded و پایان کامل فایل script.js