Spaces:
Runtime error
Runtime error
Update script.js
Browse files
script.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
// این فایل script.js نسخه نهایی شده بر اساس کد اصلی شماست.
|
| 2 |
-
//
|
| 3 |
-
//
|
| 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 |
-
// آدرس
|
| 25 |
-
|
| 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 |
-
|
|
|
|
| 215 |
.map(item => ({
|
| 216 |
-
...item, // کپی کردن تمام پراپرتی های موجود (
|
| 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
|
| 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(`
|
| 383 |
|
| 384 |
// تلاش برای تجزیه پاسخ خطا به صورت JSON اگر سرور خطای JSON برگردانده باشد
|
| 385 |
-
let serverErrorMessage = `خطا از
|
| 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))}...`; // نمایش بخشی از پاسخ خام (
|
| 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("
|
| 410 |
updateStatus("جستجو با خطا مواجه شد.", true); // پیام اصلی برای کاربر
|
| 411 |
-
updateSelectionError("
|
| 412 |
return; // خروج از تابع searchMemoirs
|
| 413 |
}
|
| 414 |
|
| 415 |
-
console.log("Query embedding received from
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 536 |
-
|
|
|
|
|
|
|
| 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.
|
| 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
|
| 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 در
|
| 627 |
|
| 628 |
-
selectAllCheckbox = document.getElementById('select_all_books'); // مطابق با ID در
|
| 629 |
// getElementsByClassName برمی گرداند HTMLCollection زنده.
|
| 630 |
-
bookCheckboxes = document.getElementsByClassName('book-checkbox'); // مطابق با class در
|
| 631 |
|
| 632 |
-
resultsPerPageSelect = document.getElementById('resultsPerPage'); // مطابق با ID در
|
| 633 |
-
similarityThresholdInput = document.getElementById('similarityThresholdInput'); // مطابق با ID در
|
| 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
|
| 742 |
|
| 743 |
// ساخت متنی که باید کپی شود با فرمت دلخواه
|
| 744 |
-
const textToCopy = `خاطره:\n${passageText}\n\nمرجع: ${referenceText}\nاز کتاب: ${bookTitleText}\n
|
|
|
|
| 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
|