/**
* 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 += `
`;
}
// 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 += `
${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 += `
${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 = `
`;
// 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 = `
`;
} else if (role === "system") {
chatMessageElement.innerHTML = `
`;
} else {
chatMessageElement.innerHTML = `
`;
}
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 += '
';
data.acts.forEach((act) => {
summary += `- ${act}
`;
});
summary += "
";
}
// Persons involved
if (data.persons && data.persons.length > 0) {
summary += '
';
summary += '
Persons Involved:
';
summary += '
';
data.persons.forEach((person) => {
summary += `-
${person.name}
${
person.role
? `${person.role}
`
: ""
}
${
person.category
? `${person.category}
`
: ""
}
`;
});
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));
}