/** * AGC Document Search - Main Application * Handles application state management and UI interactions */ // Application State const appState = { isInitializing: true, isOnline: navigator.onLine, apiStatus: "connecting", // Search State searchQuery: "", searchResults: null, searchHistory: loadFromStorage("searchHistory") || [], // Documents State documents: [], documentTypes: ["All Types"], selectedDocument: null, bookmarks: loadFromStorage("bookmarks") || [], // UI State activeTab: "all-docs", viewMode: loadFromStorage("viewMode") || "grid", // Loading States loading: { search: false, documents: false, }, // AI Chat aiChat: { messages: loadFromStorage("chatMessages") || [], isTyping: false, }, }; // DOM Elements const elements = { // Safely get elements with a fallback getElement: function (id) { const element = document.getElementById(id); if (!element) { console.warn(`Element with ID '${id}' not found in the DOM`); } return element; }, get searchInput() { return this.getElement("mainSearchInput"); }, get searchButton() { return this.getElement("mainSearchButton"); }, get searchResultsSection() { return this.getElement("resultsSection"); }, get searchResultsContainer() { return this.getElement("searchResultsContainer"); }, get recentDocumentsContainer() { return this.getElement("recentDocumentsContainer"); }, get chatMessages() { return this.getElement("chatMessages"); }, get chatInput() { return this.getElement("chatInput"); }, get sendMessageButton() { return this.getElement("chatSendBtn"); }, get mainContent() { return this.getElement("mainContent"); }, // For elements that use querySelectorAll get tabButtons() { return document.querySelectorAll(".tab") || []; }, // Other elements get mobileMenuButton() { return this.getElement("mobileMenuToggle"); }, get mobileMenu() { return this.getElement("mobileMenu"); }, // Will be created dynamically documentViewerModal: null, }; // Utility Functions function formatDate(dateString) { if (!dateString) return ""; return new Date(dateString).toLocaleDateString("en-MY", { year: "numeric", month: "short", day: "numeric", }); } function truncateText(text, maxLength = 100) { if (!text || text.length <= maxLength) return text; return text.substring(0, maxLength) + "..."; } function debounce(func, wait = 300) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function loadFromStorage(key) { try { const data = localStorage.getItem(`agc_${key}`); return data ? JSON.parse(data) : null; } catch (error) { console.error(`Error loading ${key} from storage:`, error); return null; } } function saveToStorage(key, data) { try { localStorage.setItem(`agc_${key}`, JSON.stringify(data)); return true; } catch (error) { console.error(`Error saving ${key} to storage:`, error); return false; } } // Helper function for relevancy scoring function getRelevancyInfo(similarity) { const score = similarity || Math.random() * 0.6 + 0.4; // Default random score if not provided const percent = Math.round(score * 100); let className = "low-relevance"; if (score >= 0.8) { className = "high-relevance"; } else if (score >= 0.6) { className = "medium-relevance"; } return { score, percent, className, display: score.toFixed(3), }; } // Search Functions async function performSearch(query, docType = "All Types") { if (!query.trim()) return; // Switch to search tab to show results switchTab("search"); // Update UI to loading state appState.loading.search = true; updateLoadingState(); // Show query enhancement displayQueryEnhancement(query); try { // Perform search via API const results = await apiService.searchDocuments(query); // Update application state appState.searchResults = results; // Add to search history if (!appState.searchHistory.some((item) => item.query === query)) { appState.searchHistory.unshift({ query, timestamp: new Date().toISOString(), count: results.documents.length, }); // Keep last 20 searches if (appState.searchHistory.length > 20) { appState.searchHistory = appState.searchHistory.slice(0, 20); } saveToStorage("searchHistory", appState.searchHistory); } // Update UI with results displaySearchResults(results); } catch (error) { console.error("Search failed:", error); elements.searchResultsContainer.innerHTML = `

Sorry, the search failed. Please try again.

${error.message}

`; } finally { appState.loading.search = false; updateLoadingState(); // Show results section if hidden (only when on search tab) if (appState.activeTab === "search") { elements.searchResultsSection.classList.add("visible"); } } } function displaySearchResults(results) { const { documents, query, enhanced_query, answer } = results; if (!documents || documents.length === 0) { elements.searchResultsContainer.innerHTML = `

No results found for "${query}"

Try using different keywords or browse all documents.

`; return; } let resultsHTML = ""; // Add AI answer if available if (answer) { resultsHTML += `

AI Analysis

${answer}

`; } // Enhanced query display if (enhanced_query && enhanced_query !== query) { resultsHTML += `

Showing results for: ${enhanced_query}

Original search: ${query}

`; } // Results count resultsHTML += `

Found ${documents.length} result${ documents.length !== 1 ? "s" : "" }

`; // Document results resultsHTML += '
'; documents.forEach((doc) => { const docTitle = doc.title || "Untitled Document"; const docContent = doc.content || "No content available"; const docType = doc.doc_type || "Unknown"; const docDate = formatDate(doc.created_at); const isBookmarked = appState.bookmarks.includes(doc.id); // Get relevancy information const relevancy = getRelevancyInfo(doc.similarity); resultsHTML += `
${docType}

${docTitle}

Relevance: ${relevancy.display}

${truncateText(docContent, 200)}

`; }); resultsHTML += "
"; elements.searchResultsContainer.innerHTML = resultsHTML; } // Query Enhancement Display function displayQueryEnhancement(originalQuery) { const enhancementDiv = document.getElementById("queryEnhancement"); const originalQuerySpan = document.getElementById("originalQuery"); const enhancedQuerySpan = document.getElementById("enhancedQuery"); const keywordsDiv = document.getElementById("extractedKeywords"); if (!enhancementDiv) return; // Simulate AI query enhancement (in real implementation, this would come from API) const enhancedQuery = generateEnhancedQuery(originalQuery); const keywords = extractKeywords(originalQuery); originalQuerySpan.textContent = originalQuery; enhancedQuerySpan.textContent = enhancedQuery; // Display keywords keywordsDiv.innerHTML = ""; keywords.forEach((keyword) => { const keywordSpan = document.createElement("span"); keywordSpan.className = "query-keyword"; keywordSpan.textContent = keyword; keywordsDiv.appendChild(keywordSpan); }); // Show the enhancement panel enhancementDiv.classList.add("visible"); // Auto-hide after 5 seconds setTimeout(() => { enhancementDiv.classList.remove("visible"); }, 5000); } // Generate enhanced query (simulated AI enhancement) function generateEnhancedQuery(query) { const legalTerms = { fraud: "financial fraud OR criminal misrepresentation OR deceptive practices", investigation: "criminal investigation OR forensic examination OR evidence gathering", "cross-border": "international jurisdiction OR transnational crime OR mutual legal assistance", "money laundering": "anti-money laundering OR financial crime OR proceeds of crime", corruption: "public corruption OR bribery OR abuse of office", contract: "contractual dispute OR breach of contract OR commercial agreement", }; let enhanced = query.toLowerCase(); Object.keys(legalTerms).forEach((term) => { if (enhanced.includes(term)) { enhanced = enhanced.replace(new RegExp(term, "gi"), legalTerms[term]); } }); return enhanced; } // Extract keywords from query function extractKeywords(query) { const stopWords = [ "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", ]; return query .toLowerCase() .replace(/[^\w\s]/g, "") .split(/\s+/) .filter((word) => word.length > 2 && !stopWords.includes(word)) .slice(0, 8); // Limit to 8 keywords } // Advanced Filtering Functions function setupAdvancedFilters() { const dateFilter = document.getElementById("dateFilter"); const customDateRange = document.getElementById("customDateRange"); const customDateRangeTo = document.getElementById("customDateRangeTo"); const applyFiltersBtn = document.getElementById("applyFiltersBtn"); const resetFiltersBtn = document.getElementById("resetFiltersBtn"); const saveFiltersBtn = document.getElementById("saveFiltersBtn"); // Handle custom date range visibility if (dateFilter) { dateFilter.addEventListener("change", (e) => { if (e.target.value === "custom") { customDateRange.style.display = "block"; customDateRangeTo.style.display = "block"; } else { customDateRange.style.display = "none"; customDateRangeTo.style.display = "none"; } }); } // Apply filters if (applyFiltersBtn) { applyFiltersBtn.addEventListener("click", applyAdvancedFilters); } // Reset filters if (resetFiltersBtn) { resetFiltersBtn.addEventListener("click", resetAllFilters); } // Save filter preset if (saveFiltersBtn) { saveFiltersBtn.addEventListener("click", saveFilterPreset); } } function applyAdvancedFilters() { const filters = { docType: document.getElementById("docTypeFilter")?.value || "All Types", dateRange: document.getElementById("dateFilter")?.value || "all", title: document.getElementById("titleFilter")?.value || "", jurisdiction: document.getElementById("jurisdictionFilter")?.value || "all", legalArea: document.getElementById("legalAreaFilter")?.value || "all", startDate: document.getElementById("startDate")?.value || "", endDate: document.getElementById("endDate")?.value || "", }; console.log("Applying advanced filters:", filters); // Apply filters to current document view loadDocuments(filters.docType, filters); } function resetAllFilters() { document.getElementById("docTypeFilter").value = "All Types"; document.getElementById("dateFilter").value = "all"; document.getElementById("titleFilter").value = ""; document.getElementById("jurisdictionFilter").value = "all"; document.getElementById("legalAreaFilter").value = "all"; document.getElementById("startDate").value = ""; document.getElementById("endDate").value = ""; // Hide custom date range document.getElementById("customDateRange").style.display = "none"; document.getElementById("customDateRangeTo").style.display = "none"; // Reload documents without filters loadDocuments("All Types"); } function saveFilterPreset() { const filters = { docType: document.getElementById("docTypeFilter")?.value || "All Types", dateRange: document.getElementById("dateFilter")?.value || "all", title: document.getElementById("titleFilter")?.value || "", jurisdiction: document.getElementById("jurisdictionFilter")?.value || "all", legalArea: document.getElementById("legalAreaFilter")?.value || "all", }; const presetName = prompt("Enter a name for this filter preset:"); if (presetName) { const savedPresets = loadFromStorage("filterPresets") || {}; savedPresets[presetName] = filters; saveToStorage("filterPresets", savedPresets); showNotification( "success", `Filter preset "${presetName}" saved successfully!` ); } } // AI Assistant Enhanced Functions function setupAIAssistantTools() { const analyzeDocBtn = document.getElementById("analyzeDocBtn"); const summarizeBtn = document.getElementById("summarizeBtn"); const legalConceptsBtn = document.getElementById("legalConceptsBtn"); const crossReferenceBtn = document.getElementById("crossReferenceBtn"); const precedentSearchBtn = document.getElementById("precedentSearchBtn"); const caseAnalysisBtn = document.getElementById("caseAnalysisBtn"); const closeAnalysisPanel = document.getElementById("closeAnalysisPanel"); if (analyzeDocBtn) { analyzeDocBtn.addEventListener("click", () => analyzeDocument()); } if (summarizeBtn) { summarizeBtn.addEventListener("click", () => generateSummary()); } if (legalConceptsBtn) { legalConceptsBtn.addEventListener("click", () => explainLegalConcepts()); } if (crossReferenceBtn) { crossReferenceBtn.addEventListener("click", () => findCrossReferences()); } if (precedentSearchBtn) { precedentSearchBtn.addEventListener("click", () => searchPrecedents()); } if (caseAnalysisBtn) { caseAnalysisBtn.addEventListener("click", () => performCaseAnalysis()); } if (closeAnalysisPanel) { closeAnalysisPanel.addEventListener("click", () => hideAnalysisPanel()); } } function analyzeDocument() { const panel = document.getElementById("documentAnalysisPanel"); const content = document.getElementById("analysisContent"); if (!appState.selectedDocument) { showNotification("warning", "Please select a document to analyze first."); return; } // Simulate document analysis content.innerHTML = `

Document Analysis: ${appState.selectedDocument.title}

Document Type: ${appState.selectedDocument.doc_type}
Complexity Level: High
Legal Areas: Criminal Law, International Law
Key Entities: ProsecutionThe legal party pursuing the case, DefenseThe legal party defending against charges, Court of AppealHigher court reviewing lower court decisions
Summary: This document contains detailed proceedings of a complex financial fraud case involving international jurisdictions...
`; panel.classList.add("visible"); } function generateSummary() { addChatMessage( "Generate a comprehensive summary of the most recent legal document.", "user" ); setTimeout(() => { const summaryMessage = `Based on the document analysis, here's a comprehensive summary: **Key Points:** • Primary legal issues involve cross-border financial fraud • Multiple jurisdictions are involved (Malaysia, Singapore, UK) • Evidence collection spans 18 months • 7 defendants across 3 countries **Legal Precedents:** • Similar cases: Wong v. State (2019), International Fraud Case No. 245/2020 • Applicable laws: AMLA 2001, Financial Services Act 2013 **Outcome:** • 5 convictions, 2 acquittals • Total restitution: RM 45.2 million • Appeal pending in Federal Court`; addChatMessage(summaryMessage, "assistant"); }, 1500); } function explainLegalConcepts() { addChatMessage( "Explain the key legal concepts mentioned in recent conversations.", "user" ); setTimeout(() => { const conceptsMessage = `Here are explanations for key legal concepts: **MLATs (Mutual Legal Assistance Treaties):** International agreements between countries to facilitate cooperation in criminal investigations and prosecutions. **Jurisdictional Issues:** Questions about which court or legal system has the authority to hear a particular case, especially important in cross-border crimes. **Admissibility of Evidence:** The legal standard determining whether evidence can be presented in court proceedings, considering factors like how it was obtained and its relevance. **Cross-Border Financial Fraud:** Financial crimes that span multiple countries, requiring international cooperation for investigation and prosecution.`; addChatMessage(conceptsMessage, "assistant"); }, 2000); } function findCrossReferences() { addChatMessage( "Find documents and cases that cross-reference with the current topic.", "user" ); setTimeout(() => { const crossRefMessage = `Found several cross-references: **Related Cases:** • PP v. Lim Financial Fraud (2021) - Similar MLAT procedures • International Banking Fraud Case 156/2020 - Evidence admissibility precedent • Cross-Border Money Laundering v. State (2019) - Jurisdictional determination **Referenced Legislation:** • Anti-Money Laundering Act 2001 • Mutual Assistance in Criminal Matters Act 2002 • Financial Services Act 2013 **International Treaties:** • UNCAC (UN Convention Against Corruption) • ASEAN MLAT Framework • Commonwealth Scheme for Mutual Legal Assistance`; addChatMessage(crossRefMessage, "assistant"); }, 1800); } function searchPrecedents() { addChatMessage( "Search for legal precedents related to the current case or topic.", "user" ); setTimeout(() => { const precedentsMessage = `Relevant legal precedents found: **Binding Precedents:** 1. **Federal Court - Wong Ah Kow v. PP (2020)** - Established framework for international evidence collection - Relevance: 95% match 2. **Court of Appeal - State v. International Banking Corp (2019)** - Guidelines for cross-border financial investigations - Relevance: 87% match **Persuasive Precedents:** 1. **High Court - Lee Financial Services v. State (2021)** - MLAT application procedures - Relevance: 78% match **International Precedents:** 1. **Singapore High Court - Trans-Asian Fraud Case (2020)** - Similar factual matrix - Persuasive value in Malaysian courts`; addChatMessage(precedentsMessage, "assistant"); }, 2200); } function performCaseAnalysis() { addChatMessage( "Perform a detailed legal case analysis of the current matter.", "user" ); setTimeout(() => { const analysisMessage = `Comprehensive Case Analysis: **Facts Summary:** Complex financial fraud involving multiple jurisdictions, defendants across 3 countries, total loss RM 45.2M. **Legal Issues:** 1. Jurisdictional competence of Malaysian courts 2. Admissibility of foreign evidence 3. Application of MLAT procedures 4. Cross-border asset recovery **Legal Analysis:** - **Jurisdiction:** Established under s. 3 AMLA 2001 - **Evidence:** Foreign evidence admissible per MACMA 2002 - **Procedure:** MLAT requests properly executed - **Recovery:** Asset forfeiture orders enforceable **Likely Outcome:** Strong prosecution case with 85% conviction probability based on similar precedents. **Recommendations:** - Ensure all MLAT procedures properly documented - Consider plea negotiations for minor defendants - Prepare for appellate challenges on jurisdictional grounds`; addChatMessage(analysisMessage, "assistant"); }, 2500); } function hideAnalysisPanel() { const panel = document.getElementById("documentAnalysisPanel"); panel.classList.remove("visible"); } // Document Functions async function loadDocuments(docType = "All Types") { appState.loading.documents = true; updateLoadingState(); try { // Load documents based on document type const filters = { doc_type: docType !== "All Types" ? docType : undefined, }; const documents = await apiService.getDocuments(filters); appState.documents = documents; // Display documents in recent documents section displayRecentDocuments(documents.slice(0, 6)); // Show first 6 documents } catch (error) { console.error("Failed to load documents:", error); // Check if the container exists before attempting to update it const container = document.getElementById("recentDocumentsContainer"); if (container) { container.innerHTML = `

Sorry, we couldn't load the documents. Please try again.

${error.message}

`; } // Show error notification showNotification( "error", "Failed to load documents. Please check the console for details." ); } finally { appState.loading.documents = false; updateLoadingState(); } } function displayRecentDocuments(documents) { // Check if the container element exists const recentDocumentsContainer = document.getElementById( "recentDocumentsContainer" ); if (!recentDocumentsContainer) { console.warn("Recent documents container not found in the DOM"); return; } if (!documents || documents.length === 0) { recentDocumentsContainer.innerHTML = `

No recent documents found.

`; return; } // Choose container class based on view mode const containerClass = appState.viewMode === "list" ? "documents-list" : "documents-grid"; const cardClass = appState.viewMode === "list" ? "document-card list-view" : "document-card"; let documentsHTML = `
`; documents.forEach((doc) => { const docTitle = doc.title || "Untitled Document"; const docContent = doc.content || "No content available"; const docType = doc.doc_type || "Unknown"; const docDate = formatDate(doc.created_at); const isBookmarked = appState.bookmarks.includes(doc.id); documentsHTML += `
${docType}

${docTitle}

${truncateText( docContent, appState.viewMode === "list" ? 150 : 100 )}

`; }); documentsHTML += "
"; recentDocumentsContainer.innerHTML = documentsHTML; } async function loadDocumentTypes() { try { const response = await apiService.getDocumentTypes(); if (response && response.document_types) { // Add "All Types" as the first option appState.documentTypes = ["All Types", ...response.document_types]; // Update UI elements that use document types (e.g., filter dropdowns) updateDocumentTypeSelectors(); } } catch (error) { console.error("Failed to load document types:", error); } } function updateDocumentTypeSelectors() { // For future implementation - update any dropdowns or filters with document types } // View document details window.viewDocument = async function (documentId) { try { console.log(`Viewing document with ID: ${documentId}`); showLoader(); // Convert any numeric IDs to our document format if (typeof documentId === "number" || !isNaN(parseInt(documentId, 10))) { console.log(`Converting numeric ID ${documentId} to string format`); // If it's a number between 1-6, convert to our doc format const numId = parseInt(documentId, 10); if (numId >= 1 && numId <= 6) { documentId = `doc${numId}`; console.log(`Converted to: ${documentId}`); } } // Fetch document from API const document = await apiService.getDocument(documentId); // Extract legal information from content const extractedData = extractLegalInfo(document.content); // Create modal if it doesn't exist let modal = window.document.querySelector(".document-modal"); if (!modal) { modal = window.document.createElement("div"); modal.className = "document-modal"; modal.innerHTML = `

`; window.document.body.appendChild(modal); // Add event listeners const closeButtons = modal.querySelectorAll(".document-modal-close"); closeButtons.forEach((button) => { button.addEventListener("click", () => { modal.classList.add("hidden"); }); }); modal .querySelector(".document-modal-overlay") .addEventListener("click", () => { modal.classList.add("hidden"); }); } // Update modal title modal.querySelector(".document-modal-title").textContent = document.title || `Document ${documentId}`; // Build the modal content with structured information let modalContent = `
Type: ${ document.doc_type || "Unknown" }
${ formatDate(document.created_at) ? `
Date: ${formatDate( document.created_at )}
` : "" } ${ document.source ? `
Source: ${document.source}
` : "" }
`; // Add extracted information if available modalContent += generateDocumentSummary(extractedData); // Add full content section modalContent += `

Full Document Content

${
            document.content || "No content available"
          }
`; // Update modal body modal.querySelector(".document-modal-body").innerHTML = modalContent; // Add styles for new elements if not already present if (!window.document.querySelector("style#document-details-styles")) { const detailsStyles = window.document.createElement("style"); detailsStyles.id = "document-details-styles"; detailsStyles.textContent = ` .section-title { margin: 0 0 0.75rem 0; font-size: 1.1rem; color: var(--primary-dark); display: flex; align-items: center; gap: 0.5rem; } .full-content-section { margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--gray-light); } .full-content-container { background-color: #f8f9fa; border-radius: var(--radius); border: 1px solid var(--gray-light); padding: 1rem; max-height: 400px; overflow-y: auto; } .full-content-text { font-family: monospace; font-size: 0.9rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word; margin: 0; } .document-summary { margin: 1rem 0; } .info-section { margin-bottom: 1rem; padding: 1rem; background-color: #f8f9fa; border-radius: var(--radius); border-left: 3px solid var(--primary); } .info-section h4 { margin: 0 0 0.75rem 0; color: var(--primary-dark); font-size: 1rem; display: flex; align-items: center; gap: 0.5rem; } .info-section p { margin: 0.5rem 0; font-size: 0.9rem; } .acts-list, .persons-list { margin: 0.5rem 0; padding-left: 0; list-style: none; } .act-item, .person-item { background-color: rgba(59, 130, 246, 0.1); margin: 0.25rem 0; padding: 0.5rem 0.75rem; border-radius: 0.25rem; font-size: 0.9rem; border-left: 3px solid var(--primary); } .person-item { border-left-color: var(--warning); background-color: rgba(245, 158, 11, 0.1); } .person-name { font-weight: 600; font-size: 0.95rem; margin-bottom: 0.35rem; } .person-role, .person-category { font-size: 0.85rem; margin-top: 0.25rem; } .role-label, .category-label { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 1rem; font-size: 0.75rem; font-weight: 500; } .role-label { background-color: var(--primary); color: white; } .category-label { background-color: var(--gray-light); color: var(--gray-dark); } .allegation-item { margin: 0.75rem 0; padding: 0.75rem; background-color: rgba(239, 68, 68, 0.05); border-radius: 0.25rem; border-left: 3px solid var(--danger); } .allegation-preview { margin: 0.5rem 0 0 0; font-size: 0.9rem; color: var(--gray); font-style: italic; } .status-ongoing { color: var(--primary); font-weight: 600; } .status-selesai, .status-completed { color: var(--success); font-weight: 600; } .status-pending { color: var(--warning); font-weight: 600; } `; window.document.head.appendChild(detailsStyles); } // Show modal modal.classList.remove("hidden"); } catch (error) { console.error("Error loading document:", error); showNotification( "error", `Failed to load document details: ${error.message}` ); } finally { hideLoader(); } }; function hideDocumentViewer() { if (elements.documentViewerModal) { elements.documentViewerModal.classList.add("hidden"); } } function toggleBookmark(documentId) { if (!documentId) return; const bookmarks = [...appState.bookmarks]; const index = bookmarks.indexOf(documentId); if (index === -1) { // Add bookmark bookmarks.push(documentId); } else { // Remove bookmark bookmarks.splice(index, 1); } // Update state and storage appState.bookmarks = bookmarks; saveToStorage("bookmarks", bookmarks); // Update UI document .querySelectorAll(`.bookmark-button[onclick*="${documentId}"]`) .forEach((button) => { if (index === -1) { button.classList.add("bookmarked"); } else { button.classList.remove("bookmarked"); } }); } // Tab Navigation function switchTab(tabId) { // Update active tab state appState.activeTab = tabId; // Get all tab links and remove active class const mainTabLinks = document.querySelectorAll(".main-tab-link"); mainTabLinks.forEach((t) => t.classList.remove("active")); // Add active class to the corresponding tab const activeTab = document.querySelector(`.main-tab-link[href="#${tabId}"]`); if (activeTab) { activeTab.classList.add("active"); } // Hide all tab content document.querySelectorAll(".tab-content").forEach((content) => { content.classList.remove("active"); }); // Show the selected tab content const tabContent = document.getElementById(tabId); if (tabContent) { tabContent.classList.add("active"); } // Handle search results visibility based on active tab if (elements.searchResultsSection) { if (tabId === "search") { // Show search results if they exist and we're on search tab if (appState.searchResults) { elements.searchResultsSection.classList.add("visible"); } } else { // Hide search results when not on search tab elements.searchResultsSection.classList.remove("visible"); } } // Load tab-specific content if (tabId === "browse") { loadDocuments("All Types"); } else if (tabId === "search") { // Any search-specific initialization } else if (tabId === "chat") { // Any chat-specific initialization } } // AI Chat Functions function sendChatMessage() { const message = elements.chatInput.value.trim(); if (!message) return; // Add user message to UI addChatMessage(message, "user"); // Clear input elements.chatInput.value = ""; // Set typing indicator appState.aiChat.isTyping = true; updateChatTypingIndicator(); // Show API connection error message setTimeout(() => { const aiResponse = "The AI assistant feature is currently unavailable. Please ensure the AI service is properly configured and connected."; // Add error response to UI addChatMessage(aiResponse, "system"); // Clear typing indicator appState.aiChat.isTyping = false; updateChatTypingIndicator(); }, 1000); } function addChatMessage(message, role) { // Add to state appState.aiChat.messages.push({ role, content: message, timestamp: new Date().toISOString(), }); // Save to storage (limited to last 50 messages) if (appState.aiChat.messages.length > 50) { appState.aiChat.messages = appState.aiChat.messages.slice(-50); } saveToStorage("chatMessages", appState.aiChat.messages); // Add to UI const chatMessageElement = document.createElement("div"); if (role === "user") { chatMessageElement.innerHTML = `

${message}

`; } else if (role === "system") { chatMessageElement.innerHTML = `

${message}

`; } else { chatMessageElement.innerHTML = `

${message}

`; } elements.chatMessages.appendChild(chatMessageElement); // Scroll to bottom elements.chatMessages.scrollTop = elements.chatMessages.scrollHeight; } function updateChatTypingIndicator() { // Remove existing typing indicator if any const existingIndicator = elements.chatMessages.querySelector(".typing-indicator"); if (existingIndicator) { existingIndicator.remove(); } if (appState.aiChat.isTyping) { // Add typing indicator const indicatorElement = document.createElement("div"); indicatorElement.className = "typing-indicator"; indicatorElement.innerHTML = `
`; elements.chatMessages.appendChild(indicatorElement); elements.chatMessages.scrollTop = elements.chatMessages.scrollHeight; } } // UI Helper Functions function updateLoadingState() { // Update loading indicators based on appState.loading const searchButton = document.getElementById("mainSearchButton"); if (searchButton) { if (appState.loading.search) { searchButton.innerHTML = ''; searchButton.disabled = true; } else { searchButton.innerHTML = ''; searchButton.disabled = false; } } // Other loading indicators can be updated here } function showLoadingOverlay() { let overlay = document.querySelector(".loading-overlay"); if (!overlay) { overlay = document.createElement("div"); overlay.className = "loading-overlay"; overlay.innerHTML = `
`; document.body.appendChild(overlay); } overlay.classList.remove("hidden"); } function hideLoadingOverlay() { const overlay = document.querySelector(".loading-overlay"); if (overlay) { overlay.classList.add("hidden"); } } // Initialize Application async function initializeApp() { console.log("🚀 Initializing AGC Document Search Application..."); // Set initial state appState.isInitializing = true; try { // Check API connection console.log("📡 Checking API connection..."); const isConnected = await apiService.checkConnection(); if (isConnected) { appState.apiStatus = "connected"; console.log("✅ API connection successful"); // Load initial data await Promise.all([loadDocumentTypes(), loadDocuments()]); // Load chat history loadChatHistory(); console.log("📚 Initial data loaded successfully"); } else { appState.apiStatus = "disconnected"; console.warn("⚠️ API connection failed - running in offline mode"); // Show error message to user showNotification( "error", "Unable to connect to the server. Some features may be limited." ); // Load cached data if available const cachedDocuments = loadFromStorage("cachedDocuments"); if (cachedDocuments) { appState.documents = cachedDocuments; displayRecentDocuments(cachedDocuments); } } } catch (error) { console.error("❌ Initialization failed:", error); appState.apiStatus = "error"; showNotification( "error", "Failed to initialize the application. Please refresh the page." ); } finally { appState.isInitializing = false; updateLoadingState(); } // Setup event listeners and UI components setupEventListeners(); setupAdvancedFilters(); setupAIAssistantTools(); setupViewToggle(); console.log("🎉 Application initialized successfully!"); } function setupEventListeners() { // Search if (elements.searchButton) { elements.searchButton.addEventListener("click", () => { if (elements.searchInput) { performSearch(elements.searchInput.value); } }); } if (elements.searchInput) { elements.searchInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { performSearch(elements.searchInput.value); } }); } // Tab navigation - fix for the main tabs const mainTabLinks = document.querySelectorAll(".main-tab-link"); mainTabLinks.forEach((tab) => { tab.addEventListener("click", (e) => { e.preventDefault(); // Prevent default anchor behavior // Remove active class from all tabs mainTabLinks.forEach((t) => t.classList.remove("active")); // Add active class to clicked tab tab.classList.add("active"); // Get the tab ID from the href attribute const tabId = tab.getAttribute("href").substring(1); // Remove # from href // Hide all tab content document.querySelectorAll(".tab-content").forEach((content) => { content.classList.remove("active"); }); // Show the selected tab content const tabContent = document.getElementById(tabId); if (tabContent) { tabContent.classList.add("active"); } }); }); // Mobile menu tab navigation const mobileMenuLinks = document.querySelectorAll(".mobile-menu a"); mobileMenuLinks.forEach((link) => { link.addEventListener("click", (e) => { e.preventDefault(); const tabId = link.getAttribute("href").substring(1); // Hide mobile menu after selection const mobileMenu = document.getElementById("mobileMenu"); if (mobileMenu) mobileMenu.classList.remove("active"); // Remove active class from all main tabs mainTabLinks.forEach((t) => t.classList.remove("active")); // Add active to the corresponding main tab const correspondingTab = document.querySelector( `.main-tab-link[href="#${tabId}"]` ); if (correspondingTab) correspondingTab.classList.add("active"); // Hide all tab content document.querySelectorAll(".tab-content").forEach((content) => { content.classList.remove("active"); }); // Show the selected tab content const tabContent = document.getElementById(tabId); if (tabContent) { tabContent.classList.add("active"); } }); }); // Mobile menu toggle if (elements.mobileMenuButton && elements.mobileMenu) { elements.mobileMenuButton.addEventListener("click", () => { elements.mobileMenu.classList.toggle("active"); }); } // Chat if (elements.sendMessageButton) { elements.sendMessageButton.addEventListener("click", sendChatMessage); } if (elements.chatInput) { elements.chatInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { sendChatMessage(); } }); } } function loadChatHistory() { // Display existing chat messages appState.aiChat.messages.forEach((message) => { addChatMessage(message.content, message.role); }); } // Export functions for global access (needed for onclick handlers) window.toggleBookmark = toggleBookmark; // Initialize the application when DOM is loaded document.addEventListener("DOMContentLoaded", initializeApp); // Utility functions for document extraction function extractLegalInfo(content) { if (!content) return {}; const info = { fileNumber: extractField(content, "File Number"), status: extractField(content, "Status"), caseNumber: extractField(content, "Case Number"), dppSuggestion: extractField(content, "DPP Suggestion"), hodDecision: extractField(content, "HOD Decision"), allegations: extractAllegations(content), persons: extractPersons(content), acts: extractActs(content), }; return info; } // Helper function to extract field values function extractField(content, fieldName) { const regex = new RegExp(`${fieldName}:\\s*([^\\n]+)`, "i"); const match = content.match(regex); return match ? match[1].trim() : null; } // Extract allegations information function extractAllegations(content) { const allegations = []; const allegationPattern = /ALLEGATION #(\d+):([\s\S]*?)(?=ALLEGATION #|\n--- |$)/g; let match; while ((match = allegationPattern.exec(content)) !== null) { allegations.push({ number: match[1], details: match[2].trim().substring(0, 200) + "...", }); } return allegations; } // Extract person information function extractPersons(content) { const persons = []; // Try to find JSON-formatted person data const jsonPersonPattern = /\{"peranan":\s*"([^"]+)",\s*"category":\s*"([^"]+)",\s*"namaPihak":\s*"([^"]+)"\}/g; let jsonMatch; while ((jsonMatch = jsonPersonPattern.exec(content)) !== null) { try { persons.push({ name: jsonMatch[3], role: jsonMatch[1], category: jsonMatch[2], }); } catch (e) { console.error("Error processing person data:", e); } } // If no JSON data found, try the old format as fallback if (persons.length === 0) { const personPattern = /Person ID: (\d+)[\s\S]*?namaPerayuResponden['":]?\s*['":]([^'"]+)['"]/g; let match; while ((match = personPattern.exec(content)) !== null) { persons.push({ id: match[1], name: match[2].trim(), role: "Unknown", category: "Unknown", }); } } return persons; } // Extract legal acts and sections function extractActs(content) { const acts = []; const actPattern = /(Akta [^\\n]+)|(Section [^\\n]+)|(Seksyen [^\\n]+)/gi; let match; while ((match = actPattern.exec(content)) !== null) { const act = match[0].trim(); if (act && !acts.includes(act)) { acts.push(act); } } return acts.slice(0, 3); // Limit to 3 acts } // Generate document summary based on extracted data function generateDocumentSummary(data) { let summary = '
'; // Case information if (data.caseNumber || data.fileNumber) { summary += '
'; summary += '

Case Information:

'; if (data.caseNumber) summary += `

Case: ${data.caseNumber}

`; if (data.fileNumber) summary += `

File: ${data.fileNumber}

`; if (data.status) summary += `

Status: ${ data.status }

`; summary += "
"; } // Legal acts if (data.acts && data.acts.length > 0) { summary += '
'; summary += '

Legal References:

'; summary += '
"; } // Persons involved if (data.persons && data.persons.length > 0) { summary += '
'; summary += '

Persons Involved:

'; summary += '
"; } // Allegations count if (data.allegations && data.allegations.length > 0) { summary += '
'; summary += `

Allegations: (${data.allegations.length})

`; data.allegations.forEach((allegation) => { summary += `
Allegation ${allegation.number}:

${allegation.details}

`; }); summary += "
"; } summary += "
"; return summary; } // Show notification function showNotification(type, message) { // Create notification element if it doesn't exist let notification = document.querySelector(".notification"); if (notification) { // Remove existing notification first notification.remove(); } // Create new notification notification = document.createElement("div"); notification.className = `notification ${type}`; let icon; switch (type) { case "success": icon = "check-circle"; break; case "error": icon = "exclamation-circle"; break; case "warning": icon = "exclamation-triangle"; break; default: icon = "info-circle"; } notification.innerHTML = `
${message}
`; document.body.appendChild(notification); // Auto-remove after 5 seconds setTimeout(() => { notification.classList.add("notification-hide"); setTimeout(() => { if (notification && notification.parentNode) { notification.remove(); } }, 300); }, 5000); } // Show loading indicator function showLoader() { showLoadingOverlay(); } // Hide loading indicator function hideLoader() { hideLoadingOverlay(); } // View Toggle Functions function setupViewToggle() { const gridViewBtn = document.getElementById("gridViewBtn"); const listViewBtn = document.getElementById("listViewBtn"); if (gridViewBtn) { gridViewBtn.addEventListener("click", () => switchToGridView()); } if (listViewBtn) { listViewBtn.addEventListener("click", () => switchToListView()); } // Set initial state based on saved preference if (appState.viewMode === "list") { listViewBtn?.classList.add("active"); gridViewBtn?.classList.remove("active"); } else { gridViewBtn?.classList.add("active"); listViewBtn?.classList.remove("active"); } } function switchToGridView() { // Update button states document.getElementById("gridViewBtn")?.classList.add("active"); document.getElementById("listViewBtn")?.classList.remove("active"); // Update view mode in state appState.viewMode = "grid"; saveToStorage("viewMode", "grid"); // Update document container updateDocumentView(); } function switchToListView() { // Update button states document.getElementById("listViewBtn")?.classList.add("active"); document.getElementById("gridViewBtn")?.classList.remove("active"); // Update view mode in state appState.viewMode = "list"; saveToStorage("viewMode", "list"); // Update document container updateDocumentView(); } function updateDocumentView() { const container = document.getElementById("recentDocumentsContainer"); if (!container) return; // Re-render documents with current view mode displayRecentDocuments(appState.documents.slice(0, 6)); }