Spaces:
Sleeping
Sleeping
// این کد، فایل script.js بهروزرسانی شده نهایی است که دکمه جستجو را زمانی فعال میکند که هم دادهها بارگذاری شده باشند و هم متنی در کادر جستجو وارد شده باشد. | |
// ****** تعریف متغیرهای عناصر HTML در بالاترین اسکوپ ****** | |
let searchButton; | |
let questionInput; | |
let resultsDiv; | |
let loadingStatusParagraph; | |
let selectionErrorParagraph; | |
let selectAllCheckbox; | |
let bookCheckboxes; // NodeList از تمام چک باکس های کتاب ها (به جز انتخاب همه) | |
let resultsPerPageSelect; // المان select برای تعداد نتایج | |
// ****** تعریف URL سرور پایتون برای دریافت Embedding سوال ****** | |
const EMBEDDING_SERVER_URL = 'https://montaghem630-khatere-khan.hf.space/get_embedding'; | |
// ****** متغیر برای نگهداری دادههای ترکیب شده از کتابهای انتخاب شده ****** | |
let memoirsWithEmbeddings = []; | |
// ****** نگاشت نام فایل JSON به نام کامل کتاب (برای نمایش در نتایج) ****** | |
// این لیست باید با مقادیر value چک باکس ها و نام های نمایشی در HTML مطابقت داشته باشد | |
const bookInfo = { | |
'jabe_siah.json': 'جعبه سیاه (منتخب خاطرات اسدالله علم)', | |
// اگر کتاب های دیگری دارید، اینجا اضافه کنید | |
// 'ketab_dovom.json': 'نام کتاب دوم', | |
// 'ketab_sevom.json': 'نام کتاب سوم', | |
}; | |
// ***************************************************************** | |
// تابع کمکی برای نمایش پیام وضعیت بارگذاری/پردازش | |
function updateStatus(message, isError = false) { | |
if (loadingStatusParagraph) { | |
loadingStatusParagraph.textContent = message; | |
loadingStatusParagraph.style.color = isError ? 'red' : '#666'; | |
} else { | |
console.log("Status:", message); // لاگ برای توسعه | |
} | |
} | |
// تابع کمکی برای نمایش خطای انتخاب کتاب | |
function updateSelectionError(message) { | |
if (selectionErrorParagraph) { | |
selectionErrorParagraph.textContent = message; | |
selectionErrorParagraph.style.color = 'red'; | |
} else { | |
console.error("Selection Error:", message); // لاگ برای توسعه | |
} | |
} | |
// تابع کمکی برای فعال/غیرفعال کردن دکمه جستجو | |
function setButtonEnabled(enabled) { | |
if (searchButton) { | |
searchButton.disabled = !enabled; | |
} | |
} | |
// ****** تابع جدید برای بررسی وضعیت و فعال/غیرفعال کردن دکمه جستجو ****** | |
// این تابع بررسی می کند که آیا داده ها بارگذاری شده اند و آیا کادر سوال خالی نیست | |
function checkAndEnableSearchButton() { | |
const isDataLoaded = memoirsWithEmbeddings.length > 0; | |
const isQueryNotEmpty = questionInput && questionInput.value.trim() !== ''; // چک کردن وجود questionInput قبل از دسترسی به value | |
// دکمه فقط زمانی فعال می شود که هم داده بارگذاری شده باشد و هم متن سوال خالی نباشد | |
setButtonEnabled(isDataLoaded && isQueryNotEmpty); | |
console.log(`Check Button State: Data Loaded = ${isDataLoaded}, Query Not Empty = ${isQueryNotEmpty}, Button Enabled = ${isDataLoaded && isQueryNotEmpty}`); // لاگ برای توسعه | |
} | |
// ****** تابع اصلی برای بارگذاری دادهها از فایلهای JSON کتابهای انتخاب شده ****** | |
// این تابع هر زمان که انتخاب کتاب ها تغییر می کند، داده ها را بارگذاری مجدد می کند | |
async function updateSelectedBooksData() { | |
console.log("Updating selected books data..."); // لاگ برای توسعه | |
updateStatus("در حال بارگذاری دادهها..."); // پیام برای کاربر | |
updateSelectionError(""); // پاک کردن پیام خطای قبلی | |
// setButtonEnabled(false); // غیرفعال کردن دکمه جستجو حین بارگذاری (توسط checkAndEnableSearchButton انجام می شود) | |
checkAndEnableSearchButton(); // در ابتدای بارگذاری، دکمه غیرفعال خواهد شد اگر هنوز متن نیست یا داده خالی می شود | |
// پیدا کردن چک باکس های کتاب ها که انتخاب شده اند | |
const selectedBookFiles = Array.from(bookCheckboxes) | |
.filter(checkbox => checkbox.checked) | |
.map(checkbox => checkbox.value); // value چک باکس ها نام فایل JSON است | |
console.log("Selected book files:", selectedBookFiles); // لاگ برای توسعه | |
// ****** چک کردن اینکه حداقل یک کتاب انتخاب شده باشد ****** | |
if (selectedBookFiles.length === 0) { | |
updateStatus(""); // پاک کردن پیام وضعیت | |
updateSelectionError("لطفاً حداقل یک کتاب برای جستجو انتخاب کنید."); // پیام خطا برای کاربر | |
console.warn("No books selected. Cannot load data."); // لاگ برای توسعه | |
memoirsWithEmbeddings = []; // پاک کردن داده های قبلی | |
resultsDiv.innerHTML = '<p>پس از انتخاب کتابها و وارد کردن سوال، نتایج اینجا نمایش داده میشوند.</p>'; // بازگرداندن پیام اولیه | |
checkAndEnableSearchButton(); // مطمئن می شویم دکمه غیرفعال شود | |
return; // توقف فرآیند اگر هیچ کتابی انتخاب نشده است | |
} | |
// ******************************************************** | |
memoirsWithEmbeddings = []; // پاک کردن دادههای قبلی قبل از بارگذاری جدید | |
try { | |
// بارگذاری همزمان تمام فایلهای JSON انتخاب شده | |
const fetchPromises = selectedBookFiles.map(filename => { | |
const filePath = `./${filename}`; | |
console.log(`Attempting to fetch: ${filePath}`); // لاگ برای توسعه | |
return fetch(filePath).then(response => { | |
if (!response.ok) { | |
throw new Error(`Error fetching file: ${filename} (Status: ${response.status})`); | |
} | |
return response.json(); | |
}) | |
.catch(error => { | |
console.error(`Failed to fetch or parse file ${filename}:`, error); // لاگ خطا برای توسعه | |
throw new Error(`Failed to load data for book file "${filename}".`); | |
}); | |
}); | |
const booksData = await Promise.all(fetchPromises); // انتظار برای دانلود و تجزیه همه فایل ها | |
// ترکیب دادهها از تمام فایلهای JSON بارگذاری شده | |
booksData.forEach(data => { | |
if (Array.isArray(data)) { | |
memoirsWithEmbeddings = memoirsWithEmbeddings.concat(data); | |
} else { | |
console.error("Fetched data is not an array:", data); // لاگ برای توسعه | |
} | |
}); | |
const loadedBooksCount = selectedBookFiles.length; | |
const totalPassagesLoaded = memoirsWithEmbeddings.length; | |
console.log(`Successfully loaded data from ${loadedBooksCount} book(s). Total passages loaded: ${totalPassagesLoaded}`); // لاگ برای توسعه | |
updateStatus(`دادهها از ${loadedBooksCount} کتاب با موفقیت بارگذاری شد. مجموع خاطرات: ${totalPassagesLoaded}. آماده جستجو هستید.`); // پیام برای کاربر | |
// setButtonEnabled(true); // فعال کردن دکمه جستجو پس از بارگذاری موفقیت آمیز داده (توسط checkAndEnableSearchButton انجام می شود) | |
checkAndEnableSearchButton(); // بررسی و فعال کردن دکمه پس از بارگذاری داده | |
// نتایج قبلی را پاک نمی کنیم، فقط پیام اولیه را پاک میکنیم اگر هنوز نمایش داده می شود | |
if (resultsDiv && resultsDiv.innerHTML === '<p>پس از انتخاب کتابها و وارد کردن سوال، نتایج اینجا نمایش داده میشوند.</p>') { | |
resultsDiv.innerHTML = ''; | |
} | |
} catch (error) { | |
console.error("Error loading selected books data:", error); // لاگ برای توسعه | |
updateStatus("خطا در بارگذاری دادهها.", true); // پیام برای کاربر | |
updateSelectionError(`خطا در بارگذاری داده از کتابهای انتخاب شده: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر.`); // پیام خطا برای کاربر | |
memoirsWithEmbeddings = []; // اطمینان از خالی بودن داده در صورت خطا | |
// setButtonEnabled(false); // غیرفعال نگه داشتن دکمه جستجو (توسط checkAndEnableSearchButton انجام می شود) | |
checkAndEnableSearchButton(); // مطمئن می شویم دکمه غیرفعال بماند | |
resultsDiv.innerHTML = '<p>پس از انتخاب کتابها و وارد کردن سوال، نتایج اینجا نمایش داده میشوند.</p>'; // بازگرداندن پیام اولیه | |
} | |
} | |
// تابع کمکی برای محاسبه شباهت کسینوسی بین دو بردار (بدون تغییر) | |
function cosineSimilarity(vecA, vecB) { | |
if (!vecA || !vecB || vecA.length !== vecB.length || vecA.length === 0) { | |
console.error("Cosine Similarity Error: Invalid vectors.", {vecA_length: vecA ? vecA.length : 'null', vecB_length: vecB ? vecB.length : 'null'}); | |
return 0; | |
} | |
let dotProduct = 0; | |
let magnitudeA = 0; | |
let magnitudeB = 0; | |
for (let i = 0; i < vecA.length; i++) { | |
dotProduct += vecA[i] * vecB[i]; | |
magnitudeA += vecA[i] * vecA[i]; | |
magnitudeB += vecB[i] * vecB[i]; | |
} | |
magnitudeA = Math.sqrt(magnitudeA); | |
magnitudeB = Math.sqrt(magnitudeB); | |
if (magnitudeA === 0 || magnitudeB === 0) { | |
return 0; | |
} | |
const similarity = dotProduct / (magnitudeA * magnitudeB); | |
return similarity; | |
} | |
// تابع کمکی برای حذف بخش کلمات کلیدی از متن Passage (همانند قبل) | |
function cleanPassageTextForDisplay(passage) { | |
const startDelimiter = ' <کلیدواژه ها: '; | |
const startIndex = passage.indexOf(startDelimiter); | |
if (startIndex === -1) { | |
return passage; | |
} | |
let cleanText = passage.substring(0, startIndex); | |
return cleanText.trim(); | |
} | |
// ****** تابع اصلی جستجو که هنگام کلیک دکمه یا فشردن Enter اجرا میشود ****** | |
async function searchMemoirs() { | |
console.log("Search triggered - executing search"); // لاگ برای توسعه (مشخص نیست دکمه یا Enter) | |
console.log(`Data loaded state (passages count): ${memoirsWithEmbeddings.length}`); // لاگ برای توسعه | |
// ****** چک کردن اینکه داده ها (از کتاب های انتخاب شده) بارگذاری شده باشند ****** | |
// این چک در checkAndEnableSearchButton هم هست، اما اینجا برای اطمینان بیشتر است | |
if (memoirsWithEmbeddings.length === 0) { | |
console.warn("No memoir data loaded. Cannot search."); // لاگ برای توسعه | |
updateSelectionError("لطفاً ابتدا کتابهای مورد نظر برای جستجو را انتخاب کرده و منتظر بارگذاری دادهها بمانید."); // پیام خطا برای کاربر | |
return; | |
} | |
// ************************************************************************** | |
const query = questionInput.value.trim(); | |
console.log(`Query text is: "${query}"`); // لاگ برای توسعه | |
if (!query) { | |
if (resultsDiv) { | |
resultsDiv.innerHTML = `<p>لطفاً عبارت مورد نظر برای جستجو را وارد کنید.</p>`; // پیام برای کاربر | |
} | |
console.warn("Search query is empty."); // لاگ برای توسعه | |
return; | |
} | |
updateStatus("در حال جستجو..."); // بهروزرسانی پیام وضعیت به جستجو برای کاربر | |
resultsDiv.innerHTML = ''; // پاک کردن نتایج قبلی یا پیام اولیه | |
try { | |
console.log("Requesting query embedding from Python server..."); // لاگ برای توسعه | |
const serverResponse = await fetch(EMBEDDING_SERVER_URL, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ query: query }) | |
}); | |
if (!serverResponse.ok) { | |
const errorBody = await serverResponse.text(); // بخوان به صورت متن برای اطلاعات بیشتر | |
console.error(`Server responded with status ${serverResponse.status}: ${errorBody}`); // لاگ خطا برای توسعه | |
throw new Error(`خطا از سرور (${serverResponse.status}). جزئیات بیشتر در کنسول مرورگر.`); // پیام خطا برای کاربر | |
} | |
const serverData = await serverResponse.json(); | |
const queryEmbeddingArray = serverData.embedding; | |
if (!queryEmbeddingArray || !Array.isArray(queryEmbeddingArray) || queryEmbeddingArray.length === 0) { | |
console.error("Server returned an invalid or empty embedding:", serverData); // لاگ خطا برای توسعه | |
throw new Error("سرور بردار جستجو را به درستی برنگرداند. جزئیات در کنسول مرورگر."); // پیام خطا برای کاربر | |
} | |
console.log("Query embedding received from server successfully."); // لاگ برای توسعه | |
console.log("Calculating similarities in browser..."); // لاگ برای توسعه | |
const searchResults = []; | |
// محاسبه شباهت با تمام قطعات خاطره ای که از کتاب های انتخاب شده بارگذاری شده اند | |
for (const memoir of memoirsWithEmbeddings) { | |
// اطمینان از وجود و صحت بردار embedding در آیتم خاطره | |
if (memoir.embedding && Array.isArray(memoir.embedding) && memoir.embedding.length === queryEmbeddingArray.length) { | |
const similarity = cosineSimilarity(queryEmbeddingArray, memoir.embedding); | |
// اضافه کردن تمام فیلدهای اصلی خاطره و امتیاز شباهت به نتیجه | |
searchResults.push({ ...memoir, similarity: similarity }); | |
} else { | |
// هشدار برای آیتم های بدون بردار یا با ابعاد نامعتبر (فقط در کنسول) | |
console.warn(`Skipping memoir due to missing or invalid embedding: ${memoir.book_title || 'Unknown Book'} - ${memoir.reference || 'Unknown Reference'}`); | |
} | |
} | |
console.log(`Similarity calculation complete. Found ${searchResults.length} results with valid embeddings.`); // لاگ برای توسعه | |
console.log("Sorting results by similarity..."); // لاگ برای توسعه | |
searchResults.sort((a, b) => b.similarity - a.similarity); | |
console.log("Results sorted."); // لاگ برای توسعه | |
// ****** انتخاب تعداد نتایج برتر بر اساس انتخاب کاربر ****** | |
const resultsPerPage = parseInt(resultsPerPageSelect.value, 10); // خواندن مقدار انتخاب شده و تبدیل به عدد صحیح | |
const topResults = searchResults.slice(0, resultsPerPage); // انتخاب فقط N نتیجه برتر | |
console.log(`Displaying top ${topResults.length} results based on user selection.`); // لاگ برای توسعه | |
// *********************************************************** | |
// ****** منطق نمایش نتایج ****** | |
if (resultsDiv) { | |
// نتایج قبلی را پاک کرده ایم | |
if (topResults.length === 0) { // اگر هیچ نتیجه ای یافت نشد | |
resultsDiv.innerHTML = `<p>نتیجه مرتبطی یافت نشد.</p>`; // پیام برای کاربر | |
console.log("No relevant results found."); // لاگ برای توسعه | |
} else { // اگر نتایجی یافت شد | |
console.log("Results found, updating DOM."); // لاگ برای توسعه | |
const resultsList = document.createElement('div'); | |
resultsList.classList.add('results-list'); | |
topResults.forEach(result => { | |
const resultItem = document.createElement('div'); | |
resultItem.classList.add('result-item'); | |
// حذف نمایش امتیاز شباهت | |
// const similarityElement = document.createElement('p'); | |
// similarityElement.classList.add('result-similarity'); | |
// similarityElement.textContent = `شباهت: ${result.similarity.toFixed(4)}`; | |
// نمایش نام کتاب | |
const bookTitleElement = document.createElement('p'); | |
bookTitleElement.classList.add('result-book-title'); | |
bookTitleElement.textContent = `از کتاب: ${result.book_title || 'نامشخص'}`; | |
// نمایش مرجع خاطره | |
const referenceElement = document.createElement('p'); | |
referenceElement.classList.add('result-reference'); | |
referenceElement.innerHTML = `<strong>مرجع:</strong> ${result.reference || 'نامشخص'}`; | |
// نمایش متن خاطره (با حذف کلمات کلیدی) | |
const passageElement = document.createElement('p'); | |
passageElement.classList.add('result-passage'); | |
passageElement.textContent = cleanPassageTextForDisplay(result.passage || ''); | |
// اضافه کردن عناصر به آیتم نتیجه با ترتیب جدید (متن -> مرجع -> کتاب) | |
resultItem.appendChild(passageElement); | |
resultItem.appendChild(referenceElement); | |
resultItem.appendChild(bookTitleElement); | |
resultsList.appendChild(resultItem); | |
}); | |
resultsDiv.appendChild(resultsList); | |
console.log("DOM updated with results."); // لاگ برای توسعه | |
// لاگ کردن نتایج برای توسعه | |
console.log(`Top ${topResults.length} results displayed (reference, book, and similarity):`); | |
topResults.forEach(result => { | |
console.log(` Book: ${result.book_title || 'Unknown'}, Ref: ${result.reference || 'N/A'}, Sim: ${result.similarity.toFixed(4)}`); // امتیاز را در لاگ نگه می داریم | |
}); | |
} | |
updateStatus(`جستجو به پایان رسید. ${topResults.length} نتیجه برتر نمایش داده شد.`); // بهروزرسانی پیام وضعیت پس از جستجو | |
} else { | |
console.error("Could not find resultsDiv to display results."); // لاگ برای توسعه | |
updateStatus("جستجو با خطا مواجه شد.", true); // پیام برای کاربر | |
} | |
} catch (error) { | |
console.error("Error during search:", error); // لاگ برای توسعه | |
if (resultsDiv) { | |
// نمایش پیام خطای عمومی به کاربر | |
resultsDiv.innerHTML = `<p style="color: red;">هنگام جستجو خطایی رخ داد: ${error.message || 'خطای نامشخص'}. جزئیات بیشتر در کنسول مرورگر موجود است.</p>`; | |
} | |
updateStatus("جستجو با خطا مواجه شد.", true); // پیام برای کاربر | |
} finally { | |
// در نهایت (چه موفقیت آمیز چه با خطا)، دکمه را دوباره بررسی و تنظیم وضعیت می کنیم | |
checkAndEnableSearchButton(); | |
} | |
} | |
// ****** Event Listeners و مقداردهی اولیه در زمان بارگذاری صفحه ****** | |
document.addEventListener('DOMContentLoaded', () => { | |
console.log("DOMContentLoaded fired. Attaching event listeners and initializing."); // لاگ برای توسعه | |
// پیدا کردن عناصر HTML با ID یا Class | |
searchButton = document.getElementById('searchButton'); | |
questionInput = document.getElementById('userQuestion'); | |
resultsDiv = document.getElementById('searchResults'); | |
loadingStatusParagraph = document.getElementById('loadingStatus'); | |
selectionErrorParagraph = document.getElementById('selectionError'); | |
selectAllCheckbox = document.getElementById('select_all_books'); | |
bookCheckboxes = document.querySelectorAll('.book-checkbox'); // انتخاب تمام چک باکس های با کلاس .book-checkbox | |
resultsPerPageSelect = document.getElementById('resultsPerPage'); // پیدا کردن المان select | |
// لاگ برای تأیید پیدا شدن عناصر HTML (برای توسعه) | |
console.log("Search Button found:", !!searchButton); | |
console.log("Question Input found:", !!questionInput); | |
console.log("Results Div found:", !!resultsDiv); | |
console.log("Loading Status found:", !!loadingStatusParagraph); | |
console.log("Selection Error found:", !!selectionErrorParagraph); | |
console.log("Select All Checkbox found:", !!selectAllCheckbox); | |
console.log(`Book Checkboxes found: ${bookCheckboxes.length}`); | |
console.log("Results Per Page Select found:", !!resultsPerPageSelect); | |
// اگر تمام عناصر مورد نیاز پیدا شدند، Event Listeners را اضافه کرده و مقداردهی اولیه را انجام دهید | |
if (searchButton && questionInput && resultsDiv && loadingStatusParagraph && selectionErrorParagraph && selectAllCheckbox && bookCheckboxes.length > 0 && resultsPerPageSelect) { | |
// ****** اضافه کردن Event Listener به دکمه جستجو ****** | |
searchButton.addEventListener('click', searchMemoirs); | |
console.log("Search button event listener attached."); // لاگ برای توسعه | |
// ****** اضافه کردن Event Listener برای تغییر متن در کادر سوال ****** | |
// از event 'input' استفاده می کنیم که با هر تغییری (تایپ، پیست و...) اجرا می شود | |
questionInput.addEventListener('input', () => { | |
checkAndEnableSearchButton(); // هر بار که متن تغییر کرد، وضعیت دکمه را بررسی کن | |
}); | |
// Event listener برای keypress (Enter) برای اجرای جستجو باقی می ماند | |
questionInput.addEventListener('keypress', (event) => { | |
if (event.key === 'Enter' || event.keyCode === 13) { | |
event.preventDefault(); | |
// فقط در صورتی جستجو را اجرا کن که دکمه فعال است (یعنی داده بارگذاری شده و متن خالی نیست) | |
if (!searchButton.disabled) { | |
searchMemoirs(); | |
} else { | |
console.warn("Attempted to search with Enter, but button is disabled (data not loaded or query empty)."); // لاگ برای توسعه | |
} | |
} | |
}); | |
console.log("Input change and keypress event listeners attached to question input."); // لاگ برای توسعه | |
// ****** منطق چک باکس 'انتخاب همه' ****** | |
selectAllCheckbox.addEventListener('change', () => { | |
const isChecked = selectAllCheckbox.checked; | |
bookCheckboxes.forEach(checkbox => { | |
checkbox.checked = isChecked; | |
}); | |
// بارگذاری مجدد داده ها پس از تغییر انتخاب همه (با تاخیر کم برای جلوگیری از فشردگی) | |
setTimeout(updateSelectedBooksData, 50); // تاخیر 50 میلی ثانیه | |
}); | |
// ****** اضافه کردن Event Listeners به چک باکس های کتاب ها ****** | |
bookCheckboxes.forEach(checkbox => { | |
checkbox.addEventListener('change', () => { | |
// اگر یکی از چک باکس های کتاب از حالت انتخاب خارج شد، 'انتخاب همه' را هم از حالت انتخاب خارج کن | |
if (!checkbox.checked) { | |
selectAllCheckbox.checked = false; | |
} | |
// اگر تمام چک باکس های کتاب انتخاب شدند، 'انتخاب همه' را هم انتخاب کن | |
else { | |
const allBooksSelected = Array.from(bookCheckboxes).every(cb => cb.checked); | |
if (allBooksSelected) { | |
selectAllCheckbox.checked = true; | |
} | |
} | |
// بارگذاری مجدد داده ها پس از تغییر انتخاب کتاب (با تاخیر کم) | |
setTimeout(updateSelectedBooksData, 50); // تاخیر 50 میلی ثانیه | |
}); | |
}); | |
// ****** اضافه کردن Event Listener به انتخابگر تعداد نتایج ****** | |
resultsPerPageSelect.addEventListener('change', () => { | |
console.log("Results per page changed to:", resultsPerPageSelect.value); // لاگ برای توسعه | |
}); | |
// ****** بارگذاری اولیه داده ها بر اساس انتخاب های پیش فرض هنگام بارگذاری صفحه ****** | |
// این تابع بر اساس چک باکس های پیش فرض (که در HTML تیک خورده اند) داده ها را بارگذاری می کند | |
updateSelectedBooksData(); // این فراخوانی در نهایت checkAndEnableSearchButton را صدا می زند | |
} else { | |
// اگر عناصر مورد نیاز پیدا نشدند، پیام خطا در کنسول و روی صفحه نمایش داده میشود | |
const errorMessage = "خطا: عناصر لازم صفحه پیدا نشدند. شناسههای HTML و نام کلاسها را در index.html بررسی کنید."; // پیام خطا برای کاربر | |
console.error(errorMessage); // لاگ برای توسعه | |
if (resultsDiv) { | |
resultsDiv.innerHTML = `<p style="color: red;">${errorMessage}</p>`; | |
} | |
updateStatus("راهاندازی اولیه با خطا مواجه شد.", true); // پیام برای کاربر | |
} | |
}); |