Jahadona commited on
Commit
437867a
·
verified ·
1 Parent(s): 61a94db

Update script.js

Browse files
Files changed (1) hide show
  1. script.js +335 -51
script.js CHANGED
@@ -2,7 +2,6 @@
2
  // شامل تمام اصلاحات لازم برای کار در Hugging Face Space جدید با مدل heydariAI/persian-embeddings.
3
  // تمام قابلیت های اضافه شده و رفع اشکالات شناسایی شده در فرآیند عیب یابی در آن لحاظ شده است.
4
  // این نسخه شامل مدیریت بصری وضعیت (لودینگ، آماده) با رنگ و اسپینر است.
5
- // این نسخه مشکل پیام اضافه در بخش نتایج پس از بارگذاری موفق را حل می کند.
6
 
7
 
8
  // ****** تعریف متغیرهای عناصر HTML در بالاترین اسکوپ ******
@@ -57,13 +56,6 @@ let memoirsWithEmbeddings = []; // این آرایه حاوی خاطرات با
57
  // Optional state parameter: 'loading', 'ready', 'error', '' (یا هر پیام دیگر که وضعیت خاصی را نشان ندهد)
58
  function updateStatus(message, isError = false, state = '') {
59
  if (loadingStatusElement) {
60
- // اگر پیام خالی است، المان وضعیت را مخفی می کنیم
61
- if (!message) {
62
- loadingStatusElement.style.display = 'none';
63
- loadingStatusElement.classList.remove('loading', 'ready', 'error'); // پاک کردن کلاس ها
64
- return; // پایان تابع
65
- }
66
-
67
  loadingStatusElement.textContent = message; // Set text first
68
 
69
  // پاک کردن کلاس های وضعیت قبلی
@@ -87,11 +79,13 @@ function updateStatus(message, isError = false, state = '') {
87
  // وضعیت پیش فرض یا پیام های عادی بدون وضعیت خاص
88
  loadingStatusElement.style.color = '#666'; // رنگ پیش فرض
89
  loadingStatusElement.style.fontWeight = 'normal'; // فونت نرمال پیش فرض
90
- loadingStatusElement.classList.add('status-message'); // اطمینان از وجود کلاس پایه
91
  }
92
 
93
- // نمایش المان با فلکس برای مرکز کردن محتوا (متن و اسپینر)
94
- loadingStatusElement.style.display = 'flex';
 
 
 
95
 
96
 
97
  } else {
@@ -109,17 +103,10 @@ function updateStatus(message, isError = false, state = '') {
109
  // تابع کمکی برای نمایش خطای انتخاب کتاب یا بارگذاری داده در المان selectionErrorElement
110
  function updateSelectionError(message) {
111
  if (selectionErrorElement) {
112
- // اگر پیام خالی است، المان خطا را مخفی می کنیم
113
- if (!message) {
114
- selectionErrorElement.style.display = 'none';
115
- selectionErrorElement.classList.remove('error'); // پاک کردن کلاس خطا
116
- return; // پایان تابع
117
- }
118
-
119
  selectionErrorElement.textContent = message;
120
  selectionErrorElement.style.color = 'red'; // رنگ قرمز
121
  selectionErrorElement.style.fontWeight = 'bold'; // فونت ضخیم
122
- selectionErrorElement.style.display = 'flex'; // نمایش با فلکس
123
  selectionErrorElement.classList.add('error'); // اضافه کردن کلاس خطا
124
 
125
  } else {
@@ -130,6 +117,7 @@ function updateSelectionError(message) {
130
  loadingStatusElement.textContent = '';
131
  loadingStatusElement.style.display = 'none';
132
  loadingStatusElement.classList.remove('loading', 'ready'); // پاک کردن کلاس های وضعیت
 
133
  }
134
  }
135
 
@@ -178,8 +166,8 @@ async function updateSelectedBooksData() {
178
  setButtonEnabled(false); // غیرفعال کردن دکمه جستجو
179
 
180
 
181
- if (searchResultsContainer) { // پاک کردن نتایج قبلی و نمایش پیام اولیه
182
- // نمایش پیام اولیه واضح در بخش نتایج هنگام شروع بارگذاری
183
  searchResultsContainer.innerHTML = '<p>در حال بارگذاری اطلاعات کتاب‌ها. لطفاً صبر کنید تا دکمه جستجو فعال شود...</p>';
184
  }
185
 
@@ -209,9 +197,6 @@ async function updateSelectedBooksData() {
209
  console.warn("No books selected. Cannot load data."); // لاگ
210
  memoirsWithEmbeddings = []; // پاک کردن داده های قبلی
211
  checkAndEnableSearchButton(); // مطمئن می شویم دکمه غیرفعال بماند
212
- if (searchResultsContainer) { // بازگرداندن پیام اولیه پیش فرض اگر هیچ کتابی انتخاب نشده است
213
- searchResultsContainer.innerHTML = '<p>پس از انتخاب کتاب‌ها و وارد کردن سوال، نتایج اینجا نمایش داده می‌شوند.</p>';
214
- }
215
  return; // توقف فرآیند
216
  }
217
  // ********************************************************
@@ -232,9 +217,9 @@ async function updateSelectedBooksData() {
232
  // تلاش برای خواندن متن خطا از پاسخ
233
  return response.text().then(text => {
234
  console.error(`Response body for ${filePath}: ${text}`);
235
- throw new Error(`Error fetching file: "${filename}" (Status: <span class="math-inline">\{response\.status\}\)\. Check if the file exists at "</span>{filePath}".`);
236
  }).catch(() => {
237
- throw new Error(`Error fetching file: "${filename}" (Status: <span class="math-inline">\{response\.status\}\)\. Check if the file exists at "</span>{filePath}".`);
238
  });
239
 
240
  }
@@ -264,7 +249,6 @@ async function updateSelectedBooksData() {
264
 
265
  if (Array.isArray(data)) {
266
  // فیلتر کردن آیتم هایی که بردار embedding معتبر دارند و افزودن اطلاعات کتاب به آن ها
267
- // مطمئن می شویم که آیتم یک آبجکت معتبر است و فیلد embedding را دارد
268
  const memoirsWithBookInfo = data.filter(item => item && typeof item === 'object' && item.embedding && Array.isArray(item.embedding) && item.embedding.length > 0)
269
  .map(item => ({
270
  ...item, // کپی کردن تمام پراپرتی های موجود (passage_original, passage_combined, reference, embedding, etc.)
@@ -277,7 +261,7 @@ async function updateSelectedBooksData() {
277
  // هشدار برای آیتم های نامعتبر
278
  const invalidMemoirsCount = data.length - memoirsWithBookInfo.length;
279
  if(invalidMemoirsCount > 0){
280
- console.warn(`Skipped <span class="math-inline">\{invalidMemoirsCount\} items from file "</span>{filename}" due to missing or invalid embedding or format.`); // لاگ هشدار
281
  }
282
 
283
  } else {
@@ -294,26 +278,10 @@ async function updateSelectedBooksData() {
294
  // تنظیم وضعیت به "خطا در بارگذاری"
295
  updateStatus(`داده‌ها بارگذاری شد، اما هیچ خاطره‌ای با بردار معتبر از کتاب‌های انتخاب شده (${loadedBooksCount} کتاب) یافت نشد.`, true, 'error');
296
  updateSelectionError("هیچ خاطره‌ای با بردار معتبر از کتاب‌های انتخاب شده یافت نشد. فرمت فایل‌های JSON و وجود فیلد embedding را بررسی کنید."); // پیام خطا
297
- // بازگرداندن پیام اولیه پیش فرض اگر هیچ داده ای لود نشد
298
- if (searchResultsContainer) {
299
- searchResultsContainer.innerHTML = '<p>پس از انتخاب کتاب‌ها و وارد کردن سوال، نتایج اینجا نمایش داده می‌شوند.</p>';
300
- }
301
-
302
  } else {
303
  console.log(`Successfully loaded data from ${loadedBooksCount} book(s)...`); // لاگ
304
  // تنظیم وضعیت به "آماده"
305
  updateStatus(`داده‌ها از ${loadedBooksCount} کتاب با موفقیت بارگذاری شد. مجموع خاطرات قابل جستجو: ${totalPassagesLoaded}. آماده جستجو هستید.`, false, 'ready');
306
-
307
- // ****** پاک کردن پیام اولیه در بخش نتایج پس از بارگذاری موفق داده ******
308
- if (searchResultsContainer) { // چک کردن وجود المان
309
- // بررسی اینکه آیا پیام فعلی شامل متن "در حال بارگذاری..." هست یا خیر
310
- // این کار از پاک شدن نتایج جستجو در صورت بارگذاری مجدد پس از جستجو جلوگیری می کند
311
- if (searchResultsContainer.innerHTML.includes('در حال بارگذاری اطلاعات کتاب‌ها')) {
312
- searchResultsContainer.innerHTML = '<p>پس از انتخاب کتاب‌ها و وارد کردن سوال، نتایج اینجا نمایش داده می‌شوند.</p>'; // بازگرداندن به پیام پیش فرض
313
- }
314
- }
315
- // ************************************************************************
316
-
317
  }
318
 
319
  checkAndEnableSearchButton(); // بررسی و فعال کردن دکمه
@@ -326,7 +294,7 @@ async function updateSelectedBooksData() {
326
  updateSelectionError(`خطا در بارگذاری داده‌ها: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر.`);
327
  memoirsWithEmbeddings = []; // اطمینان از خالی بودن داده در صورت خطا
328
  checkAndEnableSearchButton(); // مطمئن می شویم دکمه غیرفعال بماند
329
- if (searchResultsContainer) { // بازگرداندن پیام اولیه پیش فرض
330
  searchResultsContainer.innerHTML = '<p>پس از انتخاب کتاب‌ها و وارد کردن سوال، نتایج اینجا نمایش داده می‌شوند.</p>';
331
  }
332
  } finally {
@@ -368,7 +336,7 @@ function cosineSimilarity(vecA, vecB) {
368
  return similarity; // برگرداندن امتیاز
369
  }
370
 
371
- // تابع کمکی برای حذف بخش کلمات کلید
372
  // این تابع فعلاً برای نمایش passage_original مستقیماً استفاده نمی شود، اما نگه داشته شده است.
373
  function cleanPassageTextForDisplay(passage) {
374
  if (!passage || typeof passage !== 'string') {
@@ -411,8 +379,7 @@ async function searchMemoirs() {
411
 
412
  if (!query) {
413
  if (searchResultsContainer) {
414
- // اگر کادر جستجو خالی شد، پیام پیش فرض نمایش داده شود
415
- searchResultsContainer.innerHTML = '<p>پس از انتخاب کتاب‌ها و وارد کردن سوال، نتایج اینجا نمایش داده می‌شوند.</p>';
416
  }
417
  console.warn("Search query is empty."); // لاگ هشدار
418
  updateSelectionError("لطفاً عبارت مورد نظر برای جستجو را وارد کنید."); // پیام خطا
@@ -422,7 +389,7 @@ async function searchMemoirs() {
422
  // نمایش پیام "در حال جستجو" با وضعیت لودینگ
423
  updateStatus("در حال جستجو...", false, 'loading');
424
  updateSelectionError(""); // پاک کردن خطاهای قبلی
425
- if (searchResultsContainer) { // پاک کردن نتایج قبلی قبل از نمایش نتایج جدید
426
  searchResultsContainer.innerHTML = '';
427
  }
428
  setButtonEnabled(false); // غیرفعال کردن دکمه
@@ -458,7 +425,6 @@ async function searchMemoirs() {
458
  // تنظیم وضعیت به "خطا"
459
  updateStatus("جستجو با خطا مواجه شد.", true, 'error');
460
  updateSelectionError(serverErrorMessage + " جزئیات بیشتر در کنسول مرورگر."); // نمایش خطا
461
- // در صورت خطا، پیام پیش فرض در بخش نتایج نمایش داده نمی شود.
462
  return; // خروج
463
  }
464
 
@@ -514,4 +480,322 @@ async function searchMemoirs() {
514
 
515
 
516
  console.log("Sorting results by similarity..."); // لاگ
517
- filteredResults.sort((a, b) => b.similarity - a.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  // شامل تمام اصلاحات لازم برای کار در Hugging Face Space جدید با مدل heydariAI/persian-embeddings.
3
  // تمام قابلیت های اضافه شده و رفع اشکالات شناسایی شده در فرآیند عیب یابی در آن لحاظ شده است.
4
  // این نسخه شامل مدیریت بصری وضعیت (لودینگ، آماده) با رنگ و اسپینر است.
 
5
 
6
 
7
  // ****** تعریف متغیرهای عناصر HTML در بالاترین اسکوپ ******
 
56
  // Optional state parameter: 'loading', 'ready', 'error', '' (یا هر پیام دیگر که وضعیت خاصی را نشان ندهد)
57
  function updateStatus(message, isError = false, state = '') {
58
  if (loadingStatusElement) {
 
 
 
 
 
 
 
59
  loadingStatusElement.textContent = message; // Set text first
60
 
61
  // پاک کردن کلاس های وضعیت قبلی
 
79
  // وضعیت پیش فرض یا پیام های عادی بدون وضعیت خاص
80
  loadingStatusElement.style.color = '#666'; // رنگ پیش فرض
81
  loadingStatusElement.style.fontWeight = 'normal'; // فونت نرمال پیش فرض
 
82
  }
83
 
84
+ // مدیریت نمایش اسپینر (اسپینر با CSS و کلاس .status-message.loading کنترل می شود)
85
+ // نیازی به نمایش/پنهان کردن مستقیم اسپینر اینجا نیست اگر CSS به درستی تنظیم شده باشد.
86
+
87
+ loadingStatusElement.style.display = message ? 'flex' : 'none'; // استفاده از flex برای نمایش و مرکز کردن محتوا (متن و اسپینر)
88
+ // اگر پیام خالی باشد، المان مخفی می شود.
89
 
90
 
91
  } else {
 
103
  // تابع کمکی برای نمایش خطای انتخاب کتاب یا بارگذاری داده در المان selectionErrorElement
104
  function updateSelectionError(message) {
105
  if (selectionErrorElement) {
 
 
 
 
 
 
 
106
  selectionErrorElement.textContent = message;
107
  selectionErrorElement.style.color = 'red'; // رنگ قرمز
108
  selectionErrorElement.style.fontWeight = 'bold'; // فونت ضخیم
109
+ selectionErrorElement.style.display = message ? 'flex' : 'none'; // نمایش با فلکس
110
  selectionErrorElement.classList.add('error'); // اضافه کردن کلاس خطا
111
 
112
  } else {
 
117
  loadingStatusElement.textContent = '';
118
  loadingStatusElement.style.display = 'none';
119
  loadingStatusElement.classList.remove('loading', 'ready'); // پاک کردن کلاس های وضعیت
120
+ // اسپینر نیز توسط CSS و کلاس مدیریت می شود.
121
  }
122
  }
123
 
 
166
  setButtonEnabled(false); // غیرفعال کردن دکمه جستجو
167
 
168
 
169
+ if (searchResultsContainer) { // پاک کردن نتایج قبلی
170
+ // نمایش پیام اولیه واضح در بخش نتایج
171
  searchResultsContainer.innerHTML = '<p>در حال بارگذاری اطلاعات کتاب‌ها. لطفاً صبر کنید تا دکمه جستجو فعال شود...</p>';
172
  }
173
 
 
197
  console.warn("No books selected. Cannot load data."); // لاگ
198
  memoirsWithEmbeddings = []; // پاک کردن داده های قبلی
199
  checkAndEnableSearchButton(); // مطمئن می شویم دکمه غیرفعال بماند
 
 
 
200
  return; // توقف فرآیند
201
  }
202
  // ********************************************************
 
217
  // تلاش برای خواندن متن خطا از پاسخ
218
  return response.text().then(text => {
219
  console.error(`Response body for ${filePath}: ${text}`);
220
+ throw new Error(`Error fetching file: "${filename}" (Status: ${response.status}). Check if the file exists at "${filePath}".`);
221
  }).catch(() => {
222
+ throw new Error(`Error fetching file: "${filename}" (Status: ${response.status}). Check if the file exists at "${filePath}".`);
223
  });
224
 
225
  }
 
249
 
250
  if (Array.isArray(data)) {
251
  // فیلتر کردن آیتم هایی که بردار embedding معتبر دارند و افزودن اطلاعات کتاب به آن ها
 
252
  const memoirsWithBookInfo = data.filter(item => item && typeof item === 'object' && item.embedding && Array.isArray(item.embedding) && item.embedding.length > 0)
253
  .map(item => ({
254
  ...item, // کپی کردن تمام پراپرتی های موجود (passage_original, passage_combined, reference, embedding, etc.)
 
261
  // هشدار برای آیتم های نامعتبر
262
  const invalidMemoirsCount = data.length - memoirsWithBookInfo.length;
263
  if(invalidMemoirsCount > 0){
264
+ console.warn(`Skipped ${invalidMemoirsCount} items from file "${filename}" due to missing or invalid embedding or format.`); // لاگ هشدار
265
  }
266
 
267
  } else {
 
278
  // تنظیم وضعیت به "خطا در بارگذاری"
279
  updateStatus(`داده‌ها بارگذاری شد، اما هیچ خاطره‌ای با بردار معتبر از کتاب‌های انتخاب شده (${loadedBooksCount} کتاب) یافت نشد.`, true, 'error');
280
  updateSelectionError("هیچ خاطره‌ای با بردار معتبر از کتاب‌های انتخاب شده یافت نشد. فرمت فایل‌های JSON و وجود فیلد embedding را بررسی کنید."); // پیام خطا
 
 
 
 
 
281
  } else {
282
  console.log(`Successfully loaded data from ${loadedBooksCount} book(s)...`); // لاگ
283
  // تنظیم وضعیت به "آماده"
284
  updateStatus(`داده‌ها از ${loadedBooksCount} کتاب با موفقیت بارگذاری شد. مجموع خاطرات قابل جستجو: ${totalPassagesLoaded}. آماده جستجو هستید.`, false, 'ready');
 
 
 
 
 
 
 
 
 
 
 
285
  }
286
 
287
  checkAndEnableSearchButton(); // بررسی و فعال کردن دکمه
 
294
  updateSelectionError(`خطا در بارگذاری داده‌ها: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر.`);
295
  memoirsWithEmbeddings = []; // اطمینان از خالی بودن داده در صورت خطا
296
  checkAndEnableSearchButton(); // مطمئن می شویم دکمه غیرفعال بماند
297
+ if (searchResultsContainer) { // بازگرداندن پیام اولیه
298
  searchResultsContainer.innerHTML = '<p>پس از انتخاب کتاب‌ها و وارد کردن سوال، نتایج اینجا نمایش داده می‌شوند.</p>';
299
  }
300
  } finally {
 
336
  return similarity; // برگرداندن امتیاز
337
  }
338
 
339
+ // تابع کمکی برای حذف بخش کلمات کلیدی از متن Passage_combined
340
  // این تابع فعلاً برای نمایش passage_original مستقیماً استفاده نمی شود، اما نگه داشته شده است.
341
  function cleanPassageTextForDisplay(passage) {
342
  if (!passage || typeof passage !== 'string') {
 
379
 
380
  if (!query) {
381
  if (searchResultsContainer) {
382
+ searchResultsContainer.innerHTML = `<p>لطفاً عبارت مورد نظر برای جستجو را وارد کنید.</p>`; // پیام
 
383
  }
384
  console.warn("Search query is empty."); // لاگ هشدار
385
  updateSelectionError("لطفاً عبارت مورد نظر برای جستجو را وارد کنید."); // پیام خطا
 
389
  // نمایش پیام "در حال جستجو" با وضعیت لودینگ
390
  updateStatus("در حال جستجو...", false, 'loading');
391
  updateSelectionError(""); // پاک کردن خطاهای قبلی
392
+ if (searchResultsContainer) { // پاک کردن نتایج قبلی
393
  searchResultsContainer.innerHTML = '';
394
  }
395
  setButtonEnabled(false); // غیرفعال کردن دکمه
 
425
  // تنظیم وضعیت به "خطا"
426
  updateStatus("جستجو با خطا مواجه شد.", true, 'error');
427
  updateSelectionError(serverErrorMessage + " جزئیات بیشتر در کنسول مرورگر."); // نمایش خطا
 
428
  return; // خروج
429
  }
430
 
 
480
 
481
 
482
  console.log("Sorting results by similarity..."); // لاگ
483
+ filteredResults.sort((a, b) => b.similarity - a.similarity);
484
+ console.log("Filtered results sorted."); // لاگ
485
+
486
+ // ****** انتخاب تعداد نتایج برتر ******
487
+ let finalResultsPerPage = 10;
488
+ if (resultsPerPageSelect) {
489
+ const selectedValue = parseInt(resultsPerPageSelect.value, 10);
490
+ finalResultsPerPage = (!isNaN(selectedValue) && selectedValue > 0) ? selectedValue : 10;
491
+ } else {
492
+ console.warn("Results per page select element not found...", finalResultsPerPage); // لاگ هشدار
493
+ }
494
+
495
+ const topResults = filteredResults.slice(0, finalResultsPerPage);
496
+ console.log(`Displaying top ${topResults.length} results...`); // لاگ
497
+
498
+ // لاگ کردن نتایج برتر برای عیب یابی (اختیاری)
499
+ // console.log("Top results data before display:", topResults);
500
+
501
+
502
+ // ****** منطق نمایش نتایج در HTML ******
503
+ if (searchResultsContainer) {
504
+ if (topResults.length === 0) {
505
+ searchResultsContainer.innerHTML = `<p>نتیجه مرتبطی با آستانه شباهت مورد نظر (${currentSimilarityThreshold.toFixed(2)}) یافت نشد. سعی کنید عبارت دیگری را جستجو کنید یا آستانه را کاهش دهید.</p>`;
506
+ console.log("No relevant results found..."); // لاگ
507
+ } else {
508
+ console.log("Results found, updating DOM.");
509
+
510
+ const resultsList = document.createElement('div');
511
+ resultsList.classList.add('results-list');
512
+
513
+ topResults.forEach(result => {
514
+ const resultItem = document.createElement('div');
515
+ resultItem.classList.add('result-item');
516
+
517
+ // نمایش امتیاز شباهت
518
+ const similarityElement = document.createElement('p');
519
+ similarityElement.classList.add('result-similarity');
520
+ similarityElement.textContent = `شباهت: ${result.similarity !== undefined ? result.similarity.toFixed(4) : 'N/A'}`;
521
+ resultItem.appendChild(similarityElement);
522
+
523
+ // نمایش نام کتاب
524
+ const bookTitleElement = document.createElement('p');
525
+ bookTitleElement.classList.add('result-book-title');
526
+ bookTitleElement.textContent = `از کتاب: ${result.book_title || 'نامشخص'}`;
527
+ resultItem.appendChild(bookTitleElement);
528
+
529
+ // نمایش مرجع خاطره
530
+ const referenceElement = document.createElement('p');
531
+ referenceElement.classList.add('result-reference');
532
+ referenceElement.innerHTML = `<strong>مرجع:</strong> ${result.reference || 'نامشخص'}`;
533
+ resultItem.appendChild(referenceElement);
534
+
535
+
536
+ // ****** نمایش متن اصلی خاطره (استفاده از passage_original) ******
537
+ const passageElement = document.createElement('p');
538
+ passageElement.classList.add('result-passage');
539
+ // مستقیماً از فیلد passage_original استفاده می کنیم
540
+ passageElement.textContent = result.passage_original || 'متن خاطره موجود نیست.';
541
+ resultItem.appendChild(passageElement);
542
+ // ***************************************************************
543
+
544
+
545
+ // ****** اضافه کردن دکمه کپی ******
546
+ const copyButton = document.createElement('button');
547
+ copyButton.classList.add('copy-button');
548
+ copyButton.textContent = 'کپی متن';
549
+ // استایل های اینلاین موقت برای تست - بهتر است به style.css منتقل شوند
550
+ copyButton.style.display = 'block';
551
+ copyButton.style.marginTop = '10px';
552
+ copyButton.style.marginLeft = 'auto';
553
+ copyButton.style.marginRight = '0';
554
+ copyButton.style.backgroundColor = '#007bff';
555
+ copyButton.style.color = 'white';
556
+ copyButton.style.padding = '5px 5px';
557
+ copyButton.style.border = 'none';
558
+ copyButton.style.borderRadius = '4px';
559
+ copyButton.style.cursor = 'pointer';
560
+ copyButton.style.fontSize = '0.8em';
561
+ copyButton.style.fontFamily = "'Vazirmatn', sans-serif";
562
+ // **********************************
563
+ // Listener برای دکمه کپی به صورت Delegation در DOMContentLoaded اضافه شده است.
564
+ resultItem.appendChild(copyButton);
565
+ // **************************************************************************
566
+
567
+
568
+ resultsList.appendChild(resultItem);
569
+ });
570
+
571
+ searchResultsContainer.appendChild(resultsList);
572
+ console.log("DOM updated with results.");
573
+
574
+ // لاگ کردن نتایج برتر نمایش داده شده (اختیاری)
575
+ console.log(`Top ${topResults.length} results displayed (reference, book, similarity, passage start):`);
576
+ topResults.forEach(result => {
577
+ 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'}"`);
578
+ });
579
+ }
580
+
581
+ // به‌روزرسانی پیام وضعیت پس از جستجو
582
+ if (topResults.length > 0) {
583
+ updateStatus(`جستجو به پایان رسید. ${topResults.length} نتیجه برتر (پس از فیلتر با آستانه ${currentSimilarityThreshold.toFixed(2)}) نمایش داده شد.`, false, 'ready'); // وضعیت "آماده"
584
+ } else {
585
+ updateStatus(`جستجو به پایان رسید. هیچ نتیجه مرتبطی با آستانه شباهت مورد نظر (${currentSimilarityThreshold.toFixed(2)}) یافت نشد. سعی کنید عبارت دیگری را جستجو کنید یا آستانه را کاهش دهید.`, false, 'ready'); // وضعیت "آماده"
586
+ }
587
+
588
+
589
+ } else {
590
+ console.error("Could not find searchResultsContainer to display results."); // لاگ خطا
591
+ updateStatus("جستجو با خطا مواجه شد.", true, 'error'); // وضعیت "خطا"
592
+ updateSelectionError("المان نمایش نتایج پیدا نشد. لطفاً ساختار HTML را بررسی کنید."); // پیام خطا
593
+ }
594
+
595
+
596
+ } catch (error) {
597
+ // مدیریت خطا هنگام درخواست به Backend یا پردازش پاسخ
598
+ console.error("Error during search:", error); // لاگ خطا
599
+ if (searchResultsContainer) {
600
+ searchResultsContainer.innerHTML = `<p style=\"color: red;\">هنگام جستجو خطایی رخ داد: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر موجود است.</p>`;
601
+ }
602
+ // تنظیم وضعیت به "خطا"
603
+ updateStatus("جستجو با خطا مواجه شد.", true, 'error');
604
+ updateSelectionError(`هنگام جستجو خطایی رخ داد: ${error.message || 'خطای نامشخص'}.`); // نمایش خطا
605
+ } finally {
606
+ // در نهایت دکمه جستجو وضعیت خود را بررسی می کند.
607
+ checkAndEnableSearchButton(); // فعال کردن مجدد دکمه جستجو (اگر شرایط فراهم باشد)
608
+ }
609
+ }
610
+
611
+
612
+ // ****** بلوک DOMContentLoaded: این کد پس از بارگذاری کامل ساختار صفحه اجرا می شود ******
613
+ document.addEventListener('DOMContentLoaded', () => {
614
+ console.log("DOM fully loaded and parsed. Initializing script."); // لاگ
615
+
616
+ // ****** دریافت رفرنس المان های HTML (مطابق با ID ها و کلاس ها در index.html شما) ******
617
+ // این رفرنس ها متغیرهای سراسری تعریف شده در بالای فایل هستند.
618
+ searchButton = document.getElementById('searchButton');
619
+ userQuestionInput = document.getElementById('userQuestion'); // <--- مطابق با ID
620
+ searchResultsContainer = document.getElementById('searchResults'); // <--- مطابق با ID
621
+ loadingStatusElement = document.getElementById('loadingStatus'); // <--- مطابق با ID
622
+ selectionErrorElement = document.getElementById('selectionError'); // <--- مطابق با ID
623
+
624
+ selectAllCheckbox = document.getElementById('select_all_books'); // <--- مطابق با ID
625
+ // getElementsByClassName برمی گرداند HTMLCollection زنده.
626
+ bookCheckboxes = document.getElementsByClassName('book-checkbox'); // <--- مطابق با class
627
+
628
+ resultsPerPageSelect = document.getElementById('resultsPerPage'); // <--- مطابق با ID
629
+ similarityThresholdInput = document.getElementById('similarityThresholdInput'); // <--- مطابق با ID
630
+
631
+ loadingSpinnerElement = document.querySelector('#loadingStatus .loading-spinner'); // <--- دریافت رفرنس اسپینر
632
+
633
+
634
+ // **********************************************************************************
635
+
636
+
637
+ // ****** چک کردن وجود المان های ضروری برای جلوگیری از خطا ******
638
+ // چک می کنیم که تمام المان های مورد نیاز برای اجرای اسکریپت پیدا شده باشند.
639
+ // bookCheckboxes باید وجود داشته باشد و حداقل یک المان (چک باکس) داشته باشد.
640
+ const requiredElementsFound = searchButton && userQuestionInput && searchResultsContainer && loadingStatusElement && selectionErrorElement && selectAllCheckbox && bookCheckboxes && bookCheckboxes.length > 0 && resultsPerPageSelect && similarityThresholdInput && loadingSpinnerElement;
641
+
642
+ if (requiredElementsFound) {
643
+ console.log("All critical DOM elements found. Proceeding with initialization."); // لاگ
644
+
645
+ // ****** تنظیم Event Listeners ******
646
+
647
+ // Listener برای دکمه جستجو: فراخوانی تابع searchMemoirs هنگام کلیک
648
+ searchButton.addEventListener('click', searchMemoirs);
649
+ console.log("Search button click listener added."); // لاگ
650
+
651
+ // Listener برای کلید Enter در کادر ورودی سوال: شبیه سازی کلیک روی دکمه جستجو
652
+ userQuestionInput.addEventListener('keypress', (event) => {
653
+ if (event.key === 'Enter') {
654
+ event.preventDefault(); // جلوگیری از ارسال فرم
655
+ if (!searchButton.disabled) { // فقط اگر دکمه فعال است
656
+ searchButton.click();
657
+ console.log("Enter key pressed in search input, simulating search button click."); // لاگ
658
+ }
659
+ }
660
+ });
661
+ console.log("Search input keypress listener added."); // لاگ
662
+
663
+
664
+ // ****** Listener برای تغییر محتوای کادر سوال: بررسی وضعیت دکمه جستجو ******
665
+ // این Listener باعث می شود دکمه جستجو زمانی که متن وارد می شود فعال شود.
666
+ userQuestionInput.addEventListener('input', () => {
667
+ console.log("Search input value changed, checking button state."); // لاگ
668
+ checkAndEnableSearchButton(); // <--- فراخوانی تابع بررسی وضعیت دکمه
669
+ });
670
+ console.log("Search input 'input' listener added.");
671
+ // *********************************************************************
672
+
673
+
674
+ // Listener برای تغییر مقدار ورودی آستانه شباهت: به‌روزرسانی وضعیت دکمه
675
+ similarityThresholdInput.addEventListener('input', () => {
676
+ console.log("Similarity threshold input value changed."); // لاگ
677
+ checkAndEnableSearchButton(); // بررسی وضعیت دکمه (اگر مقدار نامعتبر شود غیرفعال می شود)
678
+ });
679
+ console.log("Similarity threshold input listener added."); // لاگ
680
+
681
+ // Listener برای تغییر انتخاب تعداد نتایج در صفحه
682
+ resultsPerPageSelect.addEventListener('change', () => {
683
+ console.log("Results per page setting changed."); // لاگ
684
+ // نیازی به checkAndEnableSearchButton نیست.
685
+ });
686
+ console.log("Results per page select listener added."); // لاگ
687
+
688
+
689
+ // Listener ها برای چک باکس 'انتخاب همه' و چک باکس های تکی کتاب ها
690
+ // تغییر در این چک باکس ها باید منجر به بارگذاری مجدد داده ها شود.
691
+
692
+ if (selectAllCheckbox) {
693
+ selectAllCheckbox.addEventListener('change', () => {
694
+ const isChecked = selectAllCheckbox.checked;
695
+ Array.from(bookCheckboxes).forEach(cb => {
696
+ cb.checked = isChecked;
697
+ });
698
+ console.log(`'Select All' checkbox changed to ${isChecked}. All book checkboxes updated.`); // لاگ
699
+ updateSelectedBooksData(); // <--- فراخوانی بارگذاری مجدد داده ها
700
+ });
701
+ console.log("'Select All' checkbox change listener added."); // لاگ
702
+ } else {
703
+ console.error("'Select All' checkbox element with ID 'select_all_books' not found."); // لاگ خطا
704
+ }
705
+
706
+ if (bookCheckboxes && bookCheckboxes.length > 0) {
707
+ Array.from(bookCheckboxes).forEach(cb => {
708
+ cb.addEventListener('change', () => {
709
+ const allOthersChecked = Array.from(bookCheckboxes).every(cb => cb.checked);
710
+ if (selectAllCheckbox) {
711
+ selectAllCheckbox.checked = allOthersChecked;
712
+ }
713
+ console.log("Individual book checkbox changed. Checking 'Select All' status."); // لاگ
714
+ updateSelectedBooksData(); // <--- فراخوانی بارگذاری مجدد داده ها
715
+ });
716
+ });
717
+ console.log("Individual book checkboxes change listeners added."); // لاگ
718
+ } else {
719
+ console.warn("No book checkboxes found with class 'book-checkbox'..."); // لاگ هشدار
720
+ }
721
+
722
+
723
+ // Listener برای دکمه های کپی (به صورت Delegation)
724
+ searchResultsContainer.addEventListener('click', (event) => {
725
+ if (event.target && event.target.classList && event.target.classList.contains('copy-button')) {
726
+ event.preventDefault(); // جلوگیری از رفتار پیش فرض
727
+
728
+ const resultItemElement = event.target.closest('.result-item');
729
+ if (resultItemElement) {
730
+ // استخراج متن از المان های نمایش داده شده
731
+ const passageText = resultItemElement.querySelector('.result-passage')?.textContent || '';
732
+ const referenceText = resultItemElement.querySelector('.result-reference')?.textContent.replace('مرجع:', '').trim() || 'نامشخص';
733
+ const bookTitleText = resultItemElement.querySelector('.result-book-title')?.textContent.replace('از کتاب:', '').trim() || 'نامشخص';
734
+ // گرفتن متن کامل عنصر شباهت (مثلا "شباهت: 0.7500")
735
+ const similarityElementText = resultItemElement.querySelector('.result-similarity')?.textContent || '';
736
+
737
+
738
+ // ساخت متنی که باید کپی شود
739
+ const textToCopy = `خاطره:\n${passageText}\n\nمرجع: ${referenceText}\nاز کتاب: ${bookTitleText}\n${similarityElementText}`;
740
+
741
+
742
+ // کپی کردن متن به کلیپ بورد
743
+ navigator.clipboard.writeText(textToCopy)
744
+ .then(() => {
745
+ event.target.textContent = 'کپی شد!';
746
+ setTimeout(() => {
747
+ event.target.textContent = 'کپی متن';
748
+ }, 2000);
749
+ console.log("Passage, reference, book title, and similarity copied."); // لاگ
750
+ })
751
+ .catch(err => {
752
+ console.error('Failed to copy text: ', err); // لاگ خطا
753
+ event.target.textContent = 'خطا در کپی';
754
+ setTimeout(() => {
755
+ event.target.textContent = 'کپی متن';
756
+ }, 2000);
757
+ });
758
+ } else {
759
+ console.warn("Result item parent not found for copy button click."); // لاگ هشدار
760
+ }
761
+ }
762
+ });
763
+ console.log("Copy button delegation click listener added."); // لاگ
764
+
765
+
766
+ // ****** راه اندازی اولیه: بارگذاری داده ها هنگام بارگذاری صفحه ******
767
+ // این تابع فرآیند بارگذاری داده از فایل های JSON بر اساس چک باکس های پیش فرض انتخاب شده در HTML را شروع می کند.
768
+ updateSelectedBooksData(); // این فراخوانی در نهایت checkAndEnableSearchButton را صدا می زند.
769
+ console.log("Initial data loading process started."); // لاگ
770
+
771
+
772
+ } else {
773
+ // اگر تمام عناصر مورد نیاز پیدا نشدند
774
+ const errorMessage = "خطا در بارگذاری صفحه: برخی یا تمام عناصر لازم (HTML) پیدا نشدند. شناسه‌های HTML و نام کلاس‌ها را در index.html بررسی کنید و مطمئن شوید همه عناصر ضروری وجود دارند."; // پیام خطا برای کاربر
775
+ console.error(errorMessage, { // لاگ جزئیات خطا
776
+ searchButton: !!searchButton,
777
+ userQuestionInput: !!userQuestionInput,
778
+ searchResultsContainer: !!searchResultsContainer,
779
+ loadingStatusElement: !!loadingStatusElement,
780
+ selectionErrorElement: !!selectionErrorElement,
781
+ selectAllCheckbox: !!selectAllCheckbox,
782
+ bookCheckboxesCount: bookCheckboxes ? bookCheckboxes.length : 0,
783
+ resultsPerPageSelect: !!resultsPerPageSelect,
784
+ similarityThresholdInput: !!similarityThresholdInput,
785
+ loadingSpinnerElement: !!loadingSpinnerElement // بررسی وجود اسپینر
786
+ });
787
+ if (searchResultsContainer) { // نمایش خطا روی صفحه
788
+ searchResultsContainer.innerHTML = `<p style=\"color: red;\">${errorMessage}</p>`;
789
+ }
790
+ // نمایش خطا در المان های وضعیت و خطای جداگانه
791
+ updateStatus("راه‌اندازی اولیه با خطا مواجه شد.", true, 'error');
792
+ updateSelectionError(errorMessage);
793
+
794
+ // غیرفعال نگه داشتن دکمه جستجو
795
+ if (searchButton) {
796
+ setButtonEnabled(false);
797
+ }
798
+ // نیازی به return نیست.
799
+ }
800
+ });
801
+ // پایان بلوک DOMContentLoaded و پایان کامل فایل script.js