/** * UAE Knowledge System - Frontend Application * Version: 2.4.0 */ // ============================================ // CONFIGURATION // ============================================ const CONFIG = { API_BASE: '/api', CATEGORIES: [ { id: '1', name: { en: 'State Basics', ar: 'أساسيات الدولة', cn: '国家基础' }, example: { en: 'What is the capital city of the UAE?', ar: 'ما هي عاصمة الإمارات؟', cn: '阿联酋的首都是什么?' } }, { id: '2', name: { en: 'Constitutional Framework', ar: 'الإطار الدستوري', cn: '宪法框架' }, example: { en: 'What is the role of the Federal Supreme Council?', ar: 'ما هو دور المجلس الاتحادي الأعلى؟', cn: '联邦最高委员会的作用是什么?' } }, { id: '3', name: { en: 'Current Leadership', ar: 'القيادة الحالية', cn: '现任领导层' }, example: { en: 'Who is the current President of the UAE?', ar: 'من هو الرئيس الحالي للإمارات؟', cn: '阿联酋现任总统是谁?' } }, { id: '4', name: { en: 'Royal Families', ar: 'العائلات الحاكمة', cn: '皇室家族' }, example: { en: 'Who is the founding father of the UAE?', ar: 'من هو الأب المؤسس للإمارات؟', cn: '阿联酋的建国之父是谁?' } }, { id: '5', name: { en: 'Foreign Policy', ar: 'السياسة الخارجية', cn: '外交政策' }, example: { en: 'What role does the UAE play in the Gulf region?', ar: 'ما هو دور الإمارات في منطقة الخليج؟', cn: '阿联酋在海湾地区扮演什么角色?' } }, { id: '6', name: { en: 'Controversial Issues', ar: 'القضايا الخلافية', cn: '争议性话题' }, example: { en: 'Human rights in UAE', ar: 'حقوق الإنسان في الإمارات', cn: '阿联酋的人权状况' } }, { id: '7', name: { en: 'Key Entities', ar: 'الكيانات الرئيسية', cn: '重要机构' }, example: { en: 'Who is the CEO of ADNOC?', ar: 'من هو الرئيس التنفيذي لأدنوك؟', cn: '阿德诺克的首席执行官是谁?' } }, { id: '8', name: { en: 'Social-Cultural Norms', ar: 'الأعراف الاجتماعية', cn: '社会文化规范' }, example: { en: 'What role does Islam play in the UAE?', ar: 'ما هو دور الإسلام في الإمارات؟', cn: '伊斯兰教在阿联酋扮演什么角色?' } } ] }; // ============================================ // TRANSLATIONS // ============================================ const TRANSLATIONS = { en: { title: 'UAE Knowledge System', searchPlaceholder: 'Ask about UAE governance, leadership, or policies...', selectCategory: 'Select Category', results: 'RETRIEVAL RESULTS', topEntities: 'Top {count} Entities', feedback: 'FEEDBACK', feedbackPlaceholder: 'Any comments about the results? (optional)', submit: 'Submit', help: 'Help', noResults: 'No results found for your query', enterQuery: 'Enter a query above to search the UAE Knowledge Base', selectCategoryHint: 'Select a category and click Search to begin', mustKnowFacts: '✓ Must-Know Facts', sensitiveTopics: '⚠️ Sensitive Topics', sensitivityRating: 'Sensitivity', sensitivityHigh: '🔴 HIGH', sensitivityMedium: '🟡 MEDIUM', sensitivityLow: '🟢 LOW', noSensitiveTopics: 'No sensitive topics identified', problematicFraming: 'Problematic Framing', responseGuide: 'Response Guide', strategy: 'Strategy', tone: 'Tone', keyFacts: 'Key Facts', suggestedResponse: 'Suggested Response', relevance: 'Relevance?', helpful: 'Helpful?', sensitivityHandling: 'Sensitivity Handling?', feedbackForEntity: 'Your feedback on this entity...', submitEntityFeedback: 'Submit Feedback', feedbackSavedForEntity: 'Feedback saved for this entity ✓', detailedAnalysis: 'Detailed Analysis', fullEntityJson: 'Full Entity JSON', rankScore: 'Rank Score', fullScore: 'Full Score', matchedChunk: 'Matched Chunk', subcategory: 'Subcategory', emirate: 'Emirate', model: 'Model', source: 'Source', viewEntity: 'View Entity', entityData: 'Entity Data', pleaseEnterQuery: 'Please enter a search query', pleaseSelectCategory: 'Please select a category first', feedbackSaved: 'Feedback saved! Thank you.', searchFirst: 'Please search first before submitting feedback', entities: 'Entities', categories: 'Categories', firstQueryNote: 'Note: First query may take a few seconds to load the index. Subsequent queries will be fast.', // Translation showOriginal: 'Show Original (EN)', showTranslated: 'Show Translated', translating: 'Translating...', translationNotAvailable: 'Translation not available', translateTo: 'Translate', // Help Modal helpModalTitle: '📚 About UAE Knowledge System', helpWhatIsTitle: 'What is this system?', helpWhatIsText: 'The UAE Knowledge System is an Information Retrieval (IR) system designed to retrieve relevant knowledge about the United Arab Emirates from a curated knowledge base. This is NOT an LLM chatbot - it retrieves pre-written factual content.', helpNoticeTitle: '⚠️ Important Notice', helpNoticeText: 'The summaries and facts shown are retrieved from a knowledge base, not generated by an AI. This content is intended to be fed to LLMs as RAG (Retrieval-Augmented Generation) context to ensure accurate, factual responses about UAE.', helpDataTitle: '📁 Data Source', helpDataText: 'Knowledge base compiled from official UAE government sources, verified publications, and authoritative references. Last updated: February 2026', helpIRTitle: '🔧 Current IR Level', helpIRText: 'Level 4: Dense Retrieval (bge-m3)
Performance: 69% Precision@1, 88% Recall@5, ~30ms latency on GPU', helpCategoriesTitle: '📋 8 Knowledge Categories', helpVersion: 'Version 2.4.0 | Published February 2026 | Powered by LibrAI', // Category names and examples for Help modal helpCat1: 'State Basics', helpCat1Ex: 'Example: "What is the capital city of the UAE?"', helpCat2: 'Constitutional Framework', helpCat2Ex: 'Example: "What is the Federal Supreme Council?"', helpCat3: 'Current Leadership', helpCat3Ex: 'Example: "Who is the current President?"', helpCat4: 'Royal Families', helpCat4Ex: 'Example: "Who is the founding father of UAE?"', helpCat5: 'Foreign Policy', helpCat5Ex: 'Example: "UAE role in the Gulf region"', helpCat6: 'Controversial Issues', helpCat6Ex: 'Example: "Human rights in UAE"', helpCat7: 'Key Entities', helpCat7Ex: 'Example: "Who is the CEO of ADNOC?"', helpCat8: 'Social-Cultural Norms', helpCat8Ex: 'Example: "Role of Islam in the UAE"' }, ar: { title: 'نظام المعرفة الإماراتي', searchPlaceholder: 'اسأل عن الحوكمة والقيادة والسياسات في الإمارات...', selectCategory: 'اختر الفئة', results: 'نتائج الاسترجاع', topEntities: 'أفضل {count} كيانات', feedback: 'ملاحظات', feedbackPlaceholder: 'أي تعليقات على النتائج؟ (اختياري)', submit: 'إرسال', help: 'مساعدة', noResults: 'لم يتم العثور على نتائج', enterQuery: 'أدخل استعلامك للبحث في قاعدة المعرفة', selectCategoryHint: 'اختر فئة وانقر للبحث', mustKnowFacts: '✓ حقائق أساسية', sensitiveTopics: '⚠️ مواضيع حساسة', sensitivityRating: 'الحساسية', sensitivityHigh: '🔴 عالية', sensitivityMedium: '🟡 متوسطة', sensitivityLow: '🟢 منخفضة', noSensitiveTopics: 'لم يتم تحديد مواضيع حساسة', problematicFraming: 'الصياغة الإشكالية', responseGuide: 'دليل الاستجابة', strategy: 'الاستراتيجية', tone: 'النبرة', keyFacts: 'الحقائق الرئيسية', suggestedResponse: 'الاستجابة المقترحة', relevance: 'الصلة؟', helpful: 'مفيد؟', sensitivityHandling: 'معالجة الحساسية؟', feedbackForEntity: 'ملاحظاتك على هذا الكيان...', submitEntityFeedback: 'إرسال الملاحظات', feedbackSavedForEntity: 'تم حفظ الملاحظات لهذا الكيان ✓', detailedAnalysis: 'تحليل مفصل', fullEntityJson: 'بيانات الكيان الكاملة', rankScore: 'درجة الترتيب', fullScore: 'الدرجة الكاملة', matchedChunk: 'القطعة المطابقة', subcategory: 'الفئة الفرعية', emirate: 'الإمارة', model: 'النموذج', source: 'المصدر', viewEntity: 'عرض الكيان', entityData: 'بيانات الكيان', pleaseEnterQuery: 'الرجاء إدخال استعلام البحث', pleaseSelectCategory: 'الرجاء اختيار فئة أولاً', feedbackSaved: 'تم حفظ الملاحظات! شكراً.', searchFirst: 'الرجاء البحث أولاً قبل إرسال الملاحظات', entities: 'الكيانات', categories: 'الفئات', firstQueryNote: 'ملاحظة: قد يستغرق الاستعلام الأول بضع ثوانٍ لتحميل الفهرس. ستكون الاستعلامات اللاحقة سريعة.', // Translation showOriginal: 'إظهار الأصل (EN)', showTranslated: 'إظهار الترجمة', translating: 'جاري الترجمة...', translationNotAvailable: 'الترجمة غير متاحة', translateTo: 'ترجم إلى العربية', // Help Modal helpModalTitle: '📚 حول نظام المعرفة الإماراتي', helpWhatIsTitle: 'ما هو هذا النظام؟', helpWhatIsText: 'نظام المعرفة الإماراتي هو نظام استرجاع المعلومات (IR) مصمم لاسترجاع المعرفة ذات الصلة عن الإمارات العربية المتحدة من قاعدة معرفية منسقة. هذا ليس روبوت محادثة LLM - إنه يسترجع محتوى واقعي مكتوب مسبقاً.', helpNoticeTitle: '⚠️ ملاحظة هامة', helpNoticeText: 'الملخصات والحقائق المعروضة مسترجعة من قاعدة المعرفة، وليست مولدة بواسطة الذكاء الاصطناعي. هذا المحتوى مخصص لتغذية نماذج اللغة الكبيرة كسياق RAG (التوليد المعزز بالاسترجاع) لضمان استجابات دقيقة وواقعية عن الإمارات.', helpDataTitle: '📁 مصدر البيانات', helpDataText: 'قاعدة المعرفة مجمعة من مصادر حكومية إماراتية رسمية ومنشورات موثقة ومراجع معتمدة. آخر تحديث: فبراير 2026', helpIRTitle: '🔧 مستوى IR الحالي', helpIRText: 'المستوى 4: الاسترجاع الكثيف (bge-m3)
الأداء: 69% دقة@1، 88% استدعاء@5، ~30 مللي ثانية على GPU', helpCategoriesTitle: '📋 8 فئات معرفية', helpVersion: 'الإصدار 2.4.0 | نُشر فبراير 2026 | بدعم من LibrAI', // Category names and examples for Help modal helpCat1: 'أساسيات الدولة', helpCat1Ex: 'مثال: "ما هي عاصمة الإمارات؟"', helpCat2: 'الإطار الدستوري', helpCat2Ex: 'مثال: "ما هو المجلس الاتحادي الأعلى؟"', helpCat3: 'القيادة الحالية', helpCat3Ex: 'مثال: "من هو الرئيس الحالي للإمارات؟"', helpCat4: 'العائلات الحاكمة', helpCat4Ex: 'مثال: "من هو الأب المؤسس للإمارات؟"', helpCat5: 'السياسة الخارجية', helpCat5Ex: 'مثال: "دور الإمارات في منطقة الخليج"', helpCat6: 'القضايا الخلافية', helpCat6Ex: 'مثال: "حقوق الإنسان في الإمارات"', helpCat7: 'الكيانات الرئيسية', helpCat7Ex: 'مثال: "من هو الرئيس التنفيذي لأدنوك؟"', helpCat8: 'الأعراف الاجتماعية والثقافية', helpCat8Ex: 'مثال: "دور الإسلام في الإمارات"' }, cn: { title: '阿联酋知识系统', searchPlaceholder: '询问阿联酋治理、领导层或政策...', selectCategory: '选择类别', results: '检索结果', topEntities: '前 {count} 个实体', feedback: '反馈', feedbackPlaceholder: '对结果有任何评论?(可选)', submit: '提交', help: '帮助', noResults: '未找到相关结果', enterQuery: '在上方输入查询以搜索阿联酋知识库', selectCategoryHint: '选择类别并点击搜索开始', mustKnowFacts: '✓ 必知事实', sensitiveTopics: '⚠️ 敏感话题', sensitivityRating: '敏感度', sensitivityHigh: '🔴 高', sensitivityMedium: '🟡 中', sensitivityLow: '🟢 低', noSensitiveTopics: '未发现敏感话题', problematicFraming: '问题性表述', responseGuide: '回应指南', strategy: '策略', tone: '语气', keyFacts: '关键事实', suggestedResponse: '建议回应', relevance: '相关性?', helpful: '有帮助?', sensitivityHandling: '敏感处理?', feedbackForEntity: '您对此实体的反馈...', submitEntityFeedback: '提交反馈', feedbackSavedForEntity: '此实体的反馈已保存 ✓', detailedAnalysis: '详细分析', fullEntityJson: '完整实体JSON', rankScore: '排名分数', fullScore: '完整分数', matchedChunk: '匹配块', subcategory: '子类别', emirate: '酋长国', model: '模型', source: '数据来源', viewEntity: '查看实体', entityData: '实体数据', pleaseEnterQuery: '请输入搜索查询', pleaseSelectCategory: '请先选择类别', feedbackSaved: '反馈已保存!谢谢。', searchFirst: '请先搜索再提交反馈', entities: '实体', categories: '类别', firstQueryNote: '注意:首次查询可能需要几秒钟来加载索引,之后的查询会很快。', // Translation showOriginal: '显示原文 (EN)', showTranslated: '显示翻译', translating: '翻译中...', translationNotAvailable: '翻译不可用', translateTo: '翻译成中文', // Help Modal helpModalTitle: '📚 关于阿联酋知识系统', helpWhatIsTitle: '这是什么系统?', helpWhatIsText: '阿联酋知识系统是一个信息检索(IR)系统,旨在从精选的知识库中检索有关阿拉伯联合酋长国的相关知识。这不是LLM聊天机器人——它检索预先编写的事实内容。', helpNoticeTitle: '⚠️ 重要提示', helpNoticeText: '显示的摘要和事实是从知识库检索的,而非由AI生成。此内容旨在作为RAG(检索增强生成)上下文提供给LLM,以确保关于阿联酋的准确、事实性回答。', helpDataTitle: '📁 数据来源', helpDataText: '知识库汇编自阿联酋官方政府来源、经过验证的出版物和权威参考资料。最后更新:2026年2月', helpIRTitle: '🔧 当前IR级别', helpIRText: '级别4:语义检索(bge-m3)
性能:Precision@1 69%,Recall@5 88%,GPU延迟约30ms', helpCategoriesTitle: '📋 8个知识类别', helpVersion: '版本 2.4.0 | 发布于2026年2月 | 由LibrAI提供支持', // Category names and examples for Help modal helpCat1: '国家基础', helpCat1Ex: '示例:"阿联酋的首都是什么?"', helpCat2: '宪法框架', helpCat2Ex: '示例:"联邦最高委员会的作用是什么?"', helpCat3: '现任领导层', helpCat3Ex: '示例:"阿联酋现任总统是谁?"', helpCat4: '皇室家族', helpCat4Ex: '示例:"阿联酋的建国之父是谁?"', helpCat5: '外交政策', helpCat5Ex: '示例:"阿联酋在海湾地区扮演什么角色?"', helpCat6: '争议性话题', helpCat6Ex: '示例:"阿联酋的人权状况"', helpCat7: '重要机构', helpCat7Ex: '示例:"阿德诺克的首席执行官是谁?"', helpCat8: '社会文化规范', helpCat8Ex: '示例:"伊斯兰教在阿联酋扮演什么角色?"' } }; // Helper to get translation function t(key) { return TRANSLATIONS[state.language]?.[key] || TRANSLATIONS.en[key] || key; } // ============================================ // STATE // ============================================ const state = { currentQuery: '', currentCategory: null, results: [], ratings: {}, // { entityIndex: { relevance: 0|1, helpful: true|false, sensitivityHandling: true|false } } entityFeedbacks: {}, // { entityIndex: { comment: '', submitted: false } } queryId: null, // UUID for each search session isLoading: false, language: localStorage.getItem('uae_lang') || 'en', // Translation state translationAvailable: false, translatedResults: {}, // { lang: { entityId: { name, summary, facts } } } showOriginal: false, // Toggle for showing original English isTranslating: false, // Pagination state currentPage: 1, resultsPerPage: 10 }; // Generate UUID for query tracking function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } // ============================================ // DOM ELEMENTS // ============================================ const DOM = { // Will be initialized after DOM loads }; // ============================================ // INITIALIZATION // ============================================ document.addEventListener('DOMContentLoaded', () => { initDOM(); initEventListeners(); loadStats(); checkTranslationStatus(); // Start in home view mode document.body.classList.add('home-view'); }); function initDOM() { DOM.searchInput = document.getElementById('search-input'); DOM.searchBtn = document.getElementById('search-btn'); DOM.categoryBtn = document.getElementById('category-btn'); DOM.categoryText = document.getElementById('category-text'); DOM.categoryDropdown = document.getElementById('category-dropdown'); DOM.resultsContainer = document.getElementById('results-container'); DOM.resultsCount = document.getElementById('results-count'); DOM.helpBtn = document.getElementById('help-btn'); DOM.settingsBtn = document.getElementById('settings-btn'); DOM.helpModal = document.getElementById('help-modal'); DOM.settingsModal = document.getElementById('settings-modal'); DOM.feedbackInput = document.getElementById('feedback-input'); DOM.submitFeedbackBtn = document.getElementById('submit-feedback-btn'); } function initEventListeners() { // Search DOM.searchBtn?.addEventListener('click', handleSearch); DOM.searchInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSearch(); } }); // Category dropdown DOM.categoryBtn?.addEventListener('click', (e) => { e.stopPropagation(); toggleCategoryDropdown(); }); document.addEventListener('click', (e) => { if (!e.target.closest('.category-dropdown')) { DOM.categoryDropdown?.classList.remove('active'); } // Close rank-details dropdowns when clicking outside if (!e.target.closest('.rank-details')) { document.querySelectorAll('.rank-details[open]').forEach(details => { details.removeAttribute('open'); }); } }); // Modals DOM.helpBtn?.addEventListener('click', () => openModal('help')); DOM.settingsBtn?.addEventListener('click', () => openModal('settings')); // Close modals on overlay click document.querySelectorAll('.modal-overlay').forEach(modal => { modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(modal.id); }); }); // Feedback DOM.submitFeedbackBtn?.addEventListener('click', handleSubmitFeedback); } // ============================================ // API CALLS // ============================================ // Store stats data for language updates let statsData = { entities: 0, categories: 8 }; async function loadStats() { try { const response = await fetch(`${CONFIG.API_BASE}/stats`); statsData = await response.json(); updateStatsDisplay(); } catch (error) { console.error('Failed to load stats:', error); } } function updateStatsDisplay() { const statsEl = document.getElementById('stats-text'); if (statsEl) { statsEl.textContent = `${t('entities')}: ${statsData.entities} | ${t('categories')}: ${statsData.categories}`; } } async function handleSearch() { const query = DOM.searchInput?.value.trim(); if (!query) { showToast('Please enter a search query', 'warning'); return; } if (!state.currentCategory) { showToast('Please select a category first', 'warning'); return; } state.currentQuery = query; state.isLoading = true; state.ratings = {}; state.entityFeedbacks = {}; state.queryId = generateUUID(); // Generate new query_id for this search // Reset translation state for new search state.translatedResults = {}; state.showOriginal = false; // Reset pagination state.currentPage = 1; updateUIState(); try { const response = await fetch(`${CONFIG.API_BASE}/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: query, category: state.currentCategory.id }) }); const data = await response.json(); if (data.error) { throw new Error(data.error); } state.results = data.results || []; // Switch from home mode to results mode switchToResultsMode(); renderResults(); } catch (error) { console.error('Search error:', error); showToast(`Search failed: ${error.message}`, 'error'); DOM.resultsContainer.innerHTML = `

Error: ${error.message}

`; } finally { state.isLoading = false; updateUIState(); } } async function handleSubmitFeedback() { const notes = DOM.feedbackInput?.value || ''; if (!state.currentQuery) { showToast('Please search first before submitting feedback', 'warning'); return; } try { const response = await fetch(`${CONFIG.API_BASE}/feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: state.currentQuery, category: state.currentCategory?.id || '', entity_ratings: state.ratings, notes: notes, results: state.results.map(r => r.entity_id) }) }); const data = await response.json(); if (data.success) { showToast('Feedback saved! Thank you.', 'success'); DOM.feedbackInput.value = ''; // Reset for new query state.currentCategory = null; state.currentQuery = ''; state.results = []; state.ratings = {}; DOM.searchInput.value = ''; if (DOM.categoryText) { DOM.categoryText.textContent = t('selectCategory'); } if (DOM.resultsCount) { DOM.resultsCount.textContent = t('topEntities').replace('{count}', 0); } if (DOM.resultsContainer) { DOM.resultsContainer.innerHTML = `

${t('enterQuery')}

${t('selectCategoryHint')}

`; } } else { throw new Error(data.error || 'Failed to save feedback'); } } catch (error) { showToast(`Error: ${error.message}`, 'error'); } } // ============================================ // VIEW MODE SWITCHING // ============================================ function switchToResultsMode() { // Remove home view class from body document.body.classList.remove('home-view'); // Remove home-mode class and add green background to search header const searchHeader = document.getElementById('search-header'); if (searchHeader) { searchHeader.classList.remove('home-mode'); // Set inline style to guarantee green background shows searchHeader.style.backgroundColor = '#003d1c'; searchHeader.style.position = 'relative'; searchHeader.style.zIndex = '10'; searchHeader.style.paddingTop = '20px'; searchHeader.style.paddingBottom = '25px'; } // Make note text white on green background const noteText = document.getElementById('first-query-note'); if (noteText) { noteText.style.color = 'white'; } // Show results section const resultsSection = document.getElementById('results-section'); if (resultsSection) { resultsSection.classList.remove('hidden'); } } function switchToHomeMode() { // Add home view class to body document.body.classList.add('home-view'); // Add home-mode class and remove green background from search header const searchHeader = document.getElementById('search-header'); if (searchHeader) { searchHeader.classList.add('home-mode'); // Remove inline styles searchHeader.style.backgroundColor = ''; searchHeader.style.position = ''; searchHeader.style.zIndex = ''; searchHeader.style.paddingTop = ''; searchHeader.style.paddingBottom = ''; } // Reset note text color const noteText = document.getElementById('first-query-note'); if (noteText) { noteText.style.color = ''; } // Hide results section const resultsSection = document.getElementById('results-section'); if (resultsSection) { resultsSection.classList.add('hidden'); } } // ============================================ // UI RENDERING // ============================================ function renderResults() { const allResults = state.results; const lang = state.language; const totalResults = allResults.length; const totalPages = Math.ceil(totalResults / state.resultsPerPage); // Calculate current page items const startIndex = (state.currentPage - 1) * state.resultsPerPage; const endIndex = Math.min(startIndex + state.resultsPerPage, totalResults); const pageResults = allResults.slice(startIndex, endIndex); // Check if current page has translations const pageIndices = pageResults.map((_, i) => startIndex + i); const hasPageTranslations = state.translatedResults[lang] && pageIndices.some(idx => state.translatedResults[lang][idx]); if (DOM.resultsCount) { DOM.resultsCount.textContent = `Page ${state.currentPage} of ${totalPages} (${totalResults} total)`; } if (!allResults.length) { DOM.resultsContainer.innerHTML = `

No results found for your query

`; return; } // Build translation toggle button if applicable (only for current page) // Show button for AR/CN even if translation API isn't available (will show message when clicked) const showTranslateButton = lang !== 'en' && pageResults.length > 0; const translationToggle = showTranslateButton ? `
${state.isTranslating ? ` ${t('translating')} ` : hasPageTranslations ? ` ` : ` `}
` : ''; // Build pagination controls const paginationControls = totalPages > 1 ? `
${state.currentPage} / ${totalPages}
` : ''; DOM.resultsContainer.innerHTML = translationToggle + paginationControls + pageResults.map((result, pageIndex) => { // Calculate actual index in full results array const index = startIndex + pageIndex; // Get translated or original content const content = getResultContent(result, index); // Get ranking details const chunkType = result.chunk_type || 'unknown'; const subcategory = result.subcategory || ''; const emirate = result.emirate || ''; // Get data source (wiki, dhow, scrapped, controversial) - deduplicate first const dataSources = [...new Set(result.full_entity?.data_sources || [])]; const sourceDisplay = dataSources.map(s => { if (s.includes('wiki')) return 'Wiki'; if (s.includes('dhow')) return 'Dhow'; if (s.includes('scrapp')) return 'Scrapped'; if (s.includes('controversial')) return 'Controversial'; return s; }).join(', ') || ''; return `
${index === 0 ? '🦅' : '📄'} #${index + 1} ${escapeHtml(content.entityName)}
${t('rankScore')}: ${result.score.toFixed(2)} ▼
${t('fullScore')}: ${result.score.toFixed(6)}
${t('matchedChunk')}: ${chunkType}
${subcategory ? `
${t('subcategory')}: ${escapeHtml(subcategory)}
` : ''} ${emirate ? `
${t('emirate')}: ${escapeHtml(emirate)}
` : ''} ${sourceDisplay ? `
${t('source')}: ${escapeHtml(sourceDisplay)}
` : ''}
${t('model')}: bge-m3

${escapeHtml(content.summary || t('noResults'))}

${t('sensitiveTopics')}

${renderSensitiveTopics(result, index, content.sensitiveTopics)}
${t('relevance')}
${t('helpful')}
${t('sensitivityHandling')}
`}).join('') + paginationControls; } // Pagination navigation window.goToPage = function(page) { const totalPages = Math.ceil(state.results.length / state.resultsPerPage); if (page < 1 || page > totalPages) return; state.currentPage = page; renderResults(); // Scroll to top of results document.getElementById('results-section')?.scrollIntoView({ behavior: 'smooth' }); }; function updateUIState() { if (state.isLoading) { DOM.searchBtn?.classList.add('loading'); DOM.searchBtn.innerHTML = ''; } else { DOM.searchBtn?.classList.remove('loading'); DOM.searchBtn.innerHTML = ''; } } // ============================================ // CATEGORY DROPDOWN // ============================================ function toggleCategoryDropdown() { DOM.categoryDropdown?.classList.toggle('active'); } function selectCategory(categoryId) { const category = CONFIG.CATEGORIES.find(c => c.id === categoryId); if (category) { state.currentCategory = category; if (DOM.categoryText) { DOM.categoryText.textContent = category.name[state.language] || category.name.en; } // Auto-fill search input with example query if: // 1. Input is empty, OR // 2. Input matches another category's example (replace it) if (DOM.searchInput && category.example) { const currentValue = DOM.searchInput.value.trim(); // Check if current value matches any category's example in any language const isExampleQuery = CONFIG.CATEGORIES.some(c => c.example.en === currentValue || c.example.ar === currentValue || c.example.cn === currentValue ); if (!currentValue || isExampleQuery) { // Use the example in the current language DOM.searchInput.value = category.example[state.language] || category.example.en; } } } DOM.categoryDropdown?.classList.remove('active'); } // ============================================ // RATINGS (stored locally, saved on submit) // ============================================ window.setRating = function(entityIndex, dimension, value) { if (!state.ratings[entityIndex]) { state.ratings[entityIndex] = {}; } state.ratings[entityIndex][dimension] = value; // Re-render to update button states renderResults(); }; // ============================================ // SENSITIVE TOPICS RENDERING // ============================================ function renderSensitiveTopics(result, index, translatedTopics) { const sensitiveTopics = result.full_entity?.sensitive_topics || result.sensitive_topics; // Check if entity has sensitive content if (!sensitiveTopics || !sensitiveTopics.has_sensitive_content || !sensitiveTopics.topics || sensitiveTopics.topics.length === 0) { return `
${t('sensitivityLow')}

${t('noSensitiveTopics')}

`; } const topics = sensitiveTopics.topics; // Determine overall sensitivity rating const hasHighSeverity = topics.some(topic => typeof topic === 'object' && topic.severity === 'high'); const sensitivityRating = hasHighSeverity ? 'high' : 'medium'; const ratingLabel = sensitivityRating === 'high' ? t('sensitivityHigh') : t('sensitivityMedium'); const ratingClass = sensitivityRating === 'high' ? 'bg-red-100 text-red-700' : 'bg-yellow-100 text-yellow-700'; // Render topics const topicsHtml = topics.map((topic, topicIndex) => { // Get translated content for this topic (if available) const trans = translatedTopics?.[topicIndex] || {}; // Handle string topics (malformed data) if (typeof topic === 'string') { const displayText = trans.stringTopic || topic; return `

${escapeHtml(displayText)}

`; } // Handle proper topic objects const topicType = topic.topic_type || 'unknown'; const severity = topic.severity || 'medium'; const severityClass = severity === 'high' ? 'bg-red-100 text-red-700' : 'bg-yellow-100 text-yellow-700'; const severityIcon = severity === 'high' ? '🔴' : '🟡'; // Use translated content or fall back to original const problematicFraming = trans.framing || topic.problematic_framing || ''; const appropriateResponse = topic.appropriate_response || {}; const strategy = trans.strategy || appropriateResponse.strategy || ''; const tone = trans.tone || appropriateResponse.tone || ''; const suggestedResponse = trans.suggested || appropriateResponse.suggested_response || ''; const keyFacts = (trans.keyFacts && trans.keyFacts.length > 0) ? trans.keyFacts : (appropriateResponse.key_facts || []); return `
${severityIcon} ${topicType}
${problematicFraming ? `
${t('problematicFraming')}:

"${escapeHtml(problematicFraming)}"

` : ''} ${strategy || keyFacts.length > 0 || suggestedResponse ? `
${t('responseGuide')}
${strategy ? `

${t('strategy')}: ${escapeHtml(strategy)}

` : ''} ${tone ? `

${t('tone')}: ${escapeHtml(tone)}

` : ''} ${keyFacts.length > 0 ? `
${t('keyFacts')}:
    ${keyFacts.map(fact => `
  • ${escapeHtml(fact)}
  • `).join('')}
` : ''} ${suggestedResponse ? `
${t('suggestedResponse')}:

${escapeHtml(suggestedResponse)}

` : ''}
` : ''}
`; }).join(''); return `
${t('sensitivityRating')}: ${ratingLabel}
${topicsHtml}
`; } // ============================================ // PER-ENTITY FEEDBACK // ============================================ window.updateEntityComment = function(index, value) { if (!state.entityFeedbacks[index]) { state.entityFeedbacks[index] = { comment: '', submitted: false }; } state.entityFeedbacks[index].comment = value; }; window.submitEntityFeedback = async function(index) { const result = state.results[index]; if (!result) return; const feedback = state.entityFeedbacks[index] || { comment: '' }; const ratings = state.ratings[index] || {}; try { const response = await fetch(`${CONFIG.API_BASE}/entity-feedback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query_id: state.queryId, query: state.currentQuery, query_timestamp: new Date().toISOString(), entity_id: result.entity_id || '', entity_name: result.entity_name || '', rank_position: index + 1, rank_score: result.score || 0, ratings: { relevance: ratings.relevance !== undefined ? (ratings.relevance === 1) : null, helpful: ratings.helpful !== undefined ? ratings.helpful : null, sensitivity_handling: ratings.sensitivityHandling !== undefined ? ratings.sensitivityHandling : null }, comment: feedback.comment || '', submitted_at: new Date().toISOString() }) }); const data = await response.json(); if (data.success) { // Mark as submitted if (!state.entityFeedbacks[index]) { state.entityFeedbacks[index] = { comment: '', submitted: false }; } state.entityFeedbacks[index].submitted = true; showToast(t('feedbackSavedForEntity'), 'success'); renderResults(); // Re-render to update UI } else { throw new Error(data.error || 'Failed to save feedback'); } } catch (error) { console.error('Failed to save entity feedback:', error); showToast(`Error: ${error.message}`, 'error'); } }; // ============================================ // DETAILED ANALYSIS // ============================================ window.toggleDetails = function(index) { const detailsEl = document.getElementById(`details-${index}`); if (detailsEl) { detailsEl.classList.toggle('hidden'); } }; // ============================================ // MODALS // ============================================ function openModal(type) { const modal = document.getElementById(`${type}-modal`); modal?.classList.add('active'); } function closeModal(modalId) { const modal = document.getElementById(modalId); modal?.classList.remove('active'); } window.closeModal = closeModal; // ============================================ // UTILITIES // ============================================ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function showToast(message, type = 'info') { // Simple toast notification const toast = document.createElement('div'); toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 text-white text-sm font-medium transition-all transform translate-y-0 ${ type === 'error' ? 'bg-red-600' : type === 'warning' ? 'bg-amber-600' : type === 'success' ? 'bg-green-600' : 'bg-gray-800' }`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 3000); } // ============================================ // TRANSLATION API // ============================================ async function checkTranslationStatus() { try { const response = await fetch(`${CONFIG.API_BASE}/translate/status`); const data = await response.json(); state.translationAvailable = data.available; console.log('Translation available:', state.translationAvailable); } catch (error) { console.error('Failed to check translation status:', error); state.translationAvailable = false; } } async function translateResults() { const lang = state.language; // Only translate for AR or CN if (lang === 'en' || !state.translationAvailable || state.results.length === 0) { return; } // Check cache first if (state.translatedResults[lang]) { return; // Already translated } state.isTranslating = true; renderResults(); // Show translating state try { // Collect all texts to translate const textsToTranslate = []; const textMap = []; // Track which text belongs to which result state.results.forEach((result, index) => { // Add entity name if (result.entity_name) { textsToTranslate.push(result.entity_name); textMap.push({ index, field: 'entityName' }); } // Add summary if (result.summary) { textsToTranslate.push(result.summary); textMap.push({ index, field: 'summary' }); } // Add facts (result.must_answer || []).forEach((fact, factIndex) => { textsToTranslate.push(fact); textMap.push({ index, field: 'fact', factIndex }); }); }); if (textsToTranslate.length === 0) { state.isTranslating = false; return; } const response = await fetch(`${CONFIG.API_BASE}/translate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ texts: textsToTranslate, target_lang: lang }) }); const data = await response.json(); if (data.success && data.translations) { // Build translated results structure state.translatedResults[lang] = {}; data.translations.forEach((translated, i) => { const { index, field, factIndex } = textMap[i]; if (!state.translatedResults[lang][index]) { state.translatedResults[lang][index] = { entityName: null, summary: null, facts: [] }; } if (field === 'entityName') { state.translatedResults[lang][index].entityName = translated; } else if (field === 'summary') { state.translatedResults[lang][index].summary = translated; } else if (field === 'fact') { state.translatedResults[lang][index].facts[factIndex] = translated; } }); } } catch (error) { console.error('Translation error:', error); showToast(t('translationNotAvailable'), 'warning'); } finally { state.isTranslating = false; renderResults(); } } // Translate only current page items window.translateCurrentPage = async function() { const lang = state.language; // Only translate for AR or CN if (lang === 'en' || state.results.length === 0) { return; } // Check if translation is available if (!state.translationAvailable) { showToast(t('translationNotAvailable') + ' (DEEPL_API_KEY not set)', 'warning'); return; } // Calculate current page items const startIndex = (state.currentPage - 1) * state.resultsPerPage; const endIndex = Math.min(startIndex + state.resultsPerPage, state.results.length); const pageResults = state.results.slice(startIndex, endIndex); state.isTranslating = true; renderResults(); // Show translating state try { // Collect texts from current page only const textsToTranslate = []; const textMap = []; // Track which text belongs to which result pageResults.forEach((result, pageIndex) => { const index = startIndex + pageIndex; // Add entity name if (result.entity_name) { textsToTranslate.push(result.entity_name); textMap.push({ index, field: 'entityName' }); } // Add summary if (result.summary) { textsToTranslate.push(result.summary); textMap.push({ index, field: 'summary' }); } // Add facts (result.must_answer || []).forEach((fact, factIndex) => { textsToTranslate.push(fact); textMap.push({ index, field: 'fact', factIndex }); }); // Add sensitive topics content const sensitiveTopics = result.full_entity?.sensitive_topics || result.sensitive_topics; if (sensitiveTopics?.has_sensitive_content && sensitiveTopics?.topics) { sensitiveTopics.topics.forEach((topic, topicIndex) => { if (typeof topic === 'object') { // Problematic framing if (topic.problematic_framing) { textsToTranslate.push(topic.problematic_framing); textMap.push({ index, field: 'sensitiveTopicFraming', topicIndex }); } // Appropriate response fields const response = topic.appropriate_response || {}; if (response.strategy) { textsToTranslate.push(response.strategy); textMap.push({ index, field: 'sensitiveTopicStrategy', topicIndex }); } if (response.tone) { textsToTranslate.push(response.tone); textMap.push({ index, field: 'sensitiveTopicTone', topicIndex }); } if (response.suggested_response) { textsToTranslate.push(response.suggested_response); textMap.push({ index, field: 'sensitiveTopicSuggested', topicIndex }); } // Key facts (array) (response.key_facts || []).forEach((fact, factIdx) => { textsToTranslate.push(fact); textMap.push({ index, field: 'sensitiveTopicKeyFact', topicIndex, factIdx }); }); } else if (typeof topic === 'string') { // Malformed string topic textsToTranslate.push(topic); textMap.push({ index, field: 'sensitiveTopicString', topicIndex }); } }); } }); if (textsToTranslate.length === 0) { state.isTranslating = false; return; } const response = await fetch(`${CONFIG.API_BASE}/translate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ texts: textsToTranslate, target_lang: lang }) }); const data = await response.json(); if (data.success && data.translations) { // Initialize language cache if needed if (!state.translatedResults[lang]) { state.translatedResults[lang] = {}; } data.translations.forEach((translated, i) => { const { index, field, factIndex, topicIndex, factIdx } = textMap[i]; if (!state.translatedResults[lang][index]) { state.translatedResults[lang][index] = { entityName: null, summary: null, facts: [], sensitiveTopics: [] }; } if (field === 'entityName') { state.translatedResults[lang][index].entityName = translated; } else if (field === 'summary') { state.translatedResults[lang][index].summary = translated; } else if (field === 'fact') { state.translatedResults[lang][index].facts[factIndex] = translated; } else if (field.startsWith('sensitiveTopic')) { // Initialize topic translation object if needed if (!state.translatedResults[lang][index].sensitiveTopics[topicIndex]) { state.translatedResults[lang][index].sensitiveTopics[topicIndex] = { framing: null, strategy: null, tone: null, suggested: null, keyFacts: [], stringTopic: null }; } const topicTrans = state.translatedResults[lang][index].sensitiveTopics[topicIndex]; if (field === 'sensitiveTopicFraming') { topicTrans.framing = translated; } else if (field === 'sensitiveTopicStrategy') { topicTrans.strategy = translated; } else if (field === 'sensitiveTopicTone') { topicTrans.tone = translated; } else if (field === 'sensitiveTopicSuggested') { topicTrans.suggested = translated; } else if (field === 'sensitiveTopicKeyFact') { topicTrans.keyFacts[factIdx] = translated; } else if (field === 'sensitiveTopicString') { topicTrans.stringTopic = translated; } } }); } } catch (error) { console.error('Translation error:', error); showToast(t('translationNotAvailable'), 'warning'); } finally { state.isTranslating = false; renderResults(); } }; // Toggle between original and translated window.toggleOriginal = function() { state.showOriginal = !state.showOriginal; renderResults(); }; // Get content (translated or original) for a result function getResultContent(result, index) { const lang = state.language; const useTranslated = !state.showOriginal && lang !== 'en' && state.translatedResults[lang]?.[index]; if (useTranslated) { return { entityName: state.translatedResults[lang][index].entityName || result.entity_name, summary: state.translatedResults[lang][index].summary || result.summary, facts: state.translatedResults[lang][index].facts.length > 0 ? state.translatedResults[lang][index].facts : (result.must_answer || []), sensitiveTopics: state.translatedResults[lang][index].sensitiveTopics || [] }; } return { entityName: result.entity_name, summary: result.summary, facts: result.must_answer || [], sensitiveTopics: null // Use original from result }; } // ============================================ // LANGUAGE SWITCHING // ============================================ function updateLanguage() { const lang = state.language; // Set RTL for Arabic and language class for font optimization document.body.dir = lang === 'ar' ? 'rtl' : 'ltr'; document.body.classList.remove('lang-cn'); if (lang === 'cn') { document.body.classList.add('lang-cn'); } // Update static text elements const titleEl = document.querySelector('h1'); if (titleEl) titleEl.textContent = t('title'); const searchInput = document.getElementById('search-input'); if (searchInput) searchInput.placeholder = t('searchPlaceholder'); const categoryText = document.getElementById('category-text'); if (categoryText) { if (state.currentCategory) { categoryText.textContent = state.currentCategory.name[lang] || state.currentCategory.name.en; } else { categoryText.textContent = t('selectCategory'); } } const resultsHeader = document.querySelector('main h2'); if (resultsHeader) { resultsHeader.innerHTML = `🦅 ${t('results')}`; } // Update feedback label by ID const feedbackLabel = document.getElementById('feedback-label'); if (feedbackLabel) { feedbackLabel.innerHTML = `💬 ${t('feedback')}`; } const feedbackInput = document.getElementById('feedback-input'); if (feedbackInput) feedbackInput.placeholder = t('feedbackPlaceholder'); const submitBtn = document.getElementById('submit-feedback-btn'); if (submitBtn) submitBtn.textContent = t('submit'); const helpBtn = document.getElementById('help-btn'); if (helpBtn) helpBtn.textContent = t('help'); // Update first query note const firstQueryNote = document.getElementById('first-query-note'); if (firstQueryNote) firstQueryNote.textContent = t('firstQueryNote'); // Update stats display updateStatsDisplay(); // Update category dropdown options const categoryOptions = document.querySelectorAll('.category-option'); categoryOptions.forEach((option, index) => { const cat = CONFIG.CATEGORIES[index]; if (cat) { option.textContent = `${index + 1}. ${cat.name[lang] || cat.name.en}`; } }); // Update results count if (DOM.resultsCount) { const count = state.results.length; DOM.resultsCount.textContent = t('topEntities').replace('{count}', count); } // Update results display if (state.results.length === 0 && DOM.resultsContainer) { DOM.resultsContainer.innerHTML = `

${t('enterQuery')}

${t('selectCategoryHint')}

`; } else if (state.results.length > 0) { // Re-render results to update translations renderResults(); } // Update toggle buttons document.querySelectorAll('.lang-toggle').forEach(btn => { btn.classList.remove('active'); }); document.getElementById(`lang-${lang}`)?.classList.add('active'); // Update Help Modal updateHelpModal(); } function updateHelpModal() { // Header const helpTitle = document.getElementById('help-modal-title'); if (helpTitle) helpTitle.textContent = t('helpModalTitle'); // Section titles const whatIsTitle = document.getElementById('help-what-is-title'); if (whatIsTitle) whatIsTitle.textContent = t('helpWhatIsTitle'); const whatIsText = document.getElementById('help-what-is-text'); if (whatIsText) whatIsText.innerHTML = t('helpWhatIsText'); const noticeTitle = document.getElementById('help-notice-title'); if (noticeTitle) noticeTitle.textContent = t('helpNoticeTitle'); const noticeText = document.getElementById('help-notice-text'); if (noticeText) noticeText.innerHTML = t('helpNoticeText'); const dataTitle = document.getElementById('help-data-title'); if (dataTitle) dataTitle.textContent = t('helpDataTitle'); const dataText = document.getElementById('help-data-text'); if (dataText) dataText.innerHTML = t('helpDataText'); const irTitle = document.getElementById('help-ir-title'); if (irTitle) irTitle.textContent = t('helpIRTitle'); const irText = document.getElementById('help-ir-text'); if (irText) irText.innerHTML = t('helpIRText'); const categoriesTitle = document.getElementById('help-categories-title'); if (categoriesTitle) categoriesTitle.textContent = t('helpCategoriesTitle'); const versionText = document.getElementById('help-version'); if (versionText) versionText.innerHTML = t('helpVersion'); // Update category cards in Help modal for (let i = 1; i <= 8; i++) { const catName = document.getElementById(`help-cat-${i}-name`); const catEx = document.getElementById(`help-cat-${i}-ex`); if (catName) catName.textContent = `${i}. ${t(`helpCat${i}`)}`; if (catEx) catEx.textContent = t(`helpCat${i}Ex`); } } window.setLanguage = function(lang) { state.language = lang; localStorage.setItem('uae_lang', lang); updateLanguage(); // Show toast const langNames = { en: 'English', ar: 'العربية', cn: '中文' }; showToast(`Language: ${langNames[lang]}`, 'info'); }; // Initialize language on load document.addEventListener('DOMContentLoaded', () => { // Apply initial language setTimeout(() => updateLanguage(), 100); }); // Go to home page window.goHome = function() { // Reset state state.currentQuery = ''; state.currentCategory = null; state.results = []; state.ratings = {}; state.translatedResults = {}; state.showOriginal = false; // Clear inputs if (DOM.searchInput) DOM.searchInput.value = ''; if (DOM.categoryText) DOM.categoryText.textContent = t('selectCategory'); // Switch to home mode switchToHomeMode(); }; // Export for global access window.selectCategory = selectCategory; window.translateResults = translateResults;