Spaces:
Running
Running
| // API Configuration | |
| const API_BASE_URL = '/api'; | |
| let userPersona = 'explorer'; // Default | |
| const chatMessages = document.getElementById('chat-messages'); | |
| const userInput = document.getElementById('user-input'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const onboardingOverlay = document.getElementById('onboarding-overlay'); | |
| const appContainer = document.querySelector('.app-container'); | |
| const switchBtn = document.getElementById('switch-btn'); | |
| const profileStatus = document.getElementById('profile-status'); | |
| const attachBtn = document.getElementById('attach-btn'); | |
| const fileUpload = document.getElementById('file-upload'); | |
| const personaTooltip = document.getElementById('persona-tooltip'); | |
| const infoTrigger = document.getElementById('info-trigger'); | |
| let isRequestInProgress = false; | |
| let currentDraftText = ""; | |
| let lastCustomFields = null; | |
| let lastFormValues = null; | |
| // Persona Capability Data | |
| const personaCapabilities = { | |
| lawyer: "<strong>Legal Professional Mode</strong><br>Optimized for deep statutory analysis, procedural advice, and drafting formal legal documents with precise terminology.", | |
| student: "<strong>Academic Mode</strong><br>Focuses on legal theory, landmark Supreme Court cases, and historical context of Indian laws.", | |
| citizen: "<strong>Empowerment Mode</strong><br>Provides simple explanations of legal rights, RTI support, and step-by-step guidance for basic legal issues.", | |
| explorer: "<strong>Orientation Mode</strong><br>General overview of the Indian judicial hierarchy, history, and fundamental legal concepts." | |
| }; | |
| function setLockdown(active) { | |
| isRequestInProgress = active; | |
| if (active) { | |
| document.body.classList.add('lockdown-active'); | |
| userInput.placeholder = "Judicial Lockdown: Analysis in progress..."; | |
| sendBtn.disabled = true; | |
| } else { | |
| document.body.classList.remove('lockdown-active'); | |
| userInput.placeholder = "Enter your legal query here..."; | |
| sendBtn.disabled = false; | |
| } | |
| } | |
| // Handle File Upload | |
| attachBtn.addEventListener('click', () => fileUpload.click()); | |
| fileUpload.addEventListener('change', async (e) => { | |
| const file = e.target.files[0]; | |
| if (!file) return; | |
| addMessage(`Uploading ${file.name}...`, 'user'); | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch(`${API_BASE_URL}/upload`, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.text) { | |
| userInput.value = `Audit this document: \n\n ${data.text.substring(0, 1000)}...`; | |
| addMessage(`Document ${file.name} processed. I have extracted the text. Click Execute to start the audit.`, 'system'); | |
| } | |
| } catch (err) { | |
| addMessage(`Error uploading file: ${err.message}`, 'system'); | |
| } | |
| }); | |
| document.querySelectorAll('.persona-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| userPersona = btn.getAttribute('data-persona'); | |
| onboardingOverlay.classList.add('hidden'); | |
| appContainer.classList.remove('blur-background'); | |
| // Update top bar status | |
| profileStatus.innerText = userPersona.charAt(0).toUpperCase() + userPersona.slice(1) + " Dashboard"; | |
| // Apply Dynamic Theme | |
| document.documentElement.setAttribute('data-theme', userPersona); | |
| // Update Wisdom Tooltip | |
| personaTooltip.innerHTML = personaCapabilities[userPersona]; | |
| addMessage(`Welcome! You are now connected as a **${userPersona.toUpperCase()}**. How can I help you navigate the legal system today?`, 'system'); | |
| }); | |
| }); | |
| function highlightCitations(text) { | |
| // Regex to find common Indian legal citations (e.g., Section 302, Article 21, IPC 302) | |
| const citationRegex = /\b(Section|Article|Act|Clause)\s+\d+[A-Z]?\b|\b(IPC|BNS|CRPC|CPC|DPDPA)\s+\d+[A-Z]?\b/gi; | |
| return text.replace(citationRegex, (match) => { | |
| return `<span class="legal-citation" title="Judicial Reference">${match}</span>`; | |
| }); | |
| } | |
| switchBtn.addEventListener('click', () => { | |
| onboardingOverlay.classList.remove('hidden'); | |
| appContainer.classList.add('blur-background'); | |
| }); | |
| // Motto Carousel Data | |
| const mottos = [ | |
| { text: "सत्यमेव जयते", lang: "Hindi" }, | |
| { text: "সত্যমেব জয়তে", lang: "Bengali" }, | |
| { text: "सत्यमेव जयते", lang: "Marathi" }, | |
| { text: "సత్యమేవ జయతే", lang: "Telugu" }, | |
| { text: "சத்யமேவ ஜெயதே", lang: "Tamil" }, | |
| { text: "સત્યમેવ જયતે", lang: "Gujarati" }, | |
| { text: "ستیہ میو جیتے", lang: "Urdu" }, | |
| { text: "ಸತ್ಯಮೇವ ಜಯತೇ", lang: "Kannada" }, | |
| { text: "ସତ୍ୟମେବ ଜୟତେ", lang: "Odia" }, | |
| { text: "സത്യമേവ ജയതേ", lang: "Malayalam" } | |
| ]; | |
| let currentMottoIndex = 0; | |
| const mottoText = document.getElementById('motto-text'); | |
| const mottoLang = document.getElementById('motto-lang'); | |
| function updateMotto() { | |
| mottoText.classList.add('fade-out'); | |
| mottoLang.classList.add('fade-out'); | |
| setTimeout(() => { | |
| currentMottoIndex = (currentMottoIndex + 1) % mottos.length; | |
| mottoText.innerText = mottos[currentMottoIndex].text; | |
| mottoLang.innerText = mottos[currentMottoIndex].lang; | |
| mottoText.classList.remove('fade-out'); | |
| mottoLang.classList.remove('fade-out'); | |
| }, 500); | |
| } | |
| setInterval(updateMotto, 4000); // Change every 4 seconds | |
| function addMessage(text, sender) { | |
| if (text.includes('===REQUEST_NOTICE_FORM===')) { | |
| try { | |
| const parts = text.split('===REQUEST_NOTICE_FORM==='); | |
| const jsonStr = parts[1].trim(); | |
| const customFields = JSON.parse(jsonStr); | |
| addNoticeForm(customFields); | |
| } catch (e) { | |
| console.error("Failed to parse custom fields from LLM", e); | |
| addNoticeForm(["Sender Details (Name, Address)", "Receiver Details (Name, Address)", "Grievance / Cause of Action", "Relief / Demand"]); | |
| } | |
| return; | |
| } | |
| const msgDiv = document.createElement('div'); | |
| msgDiv.className = `message ${sender}`; // Explicitly set classes | |
| // Highlight Citations | |
| let highlightedText = highlightCitations(text); | |
| // Add Spark Icon for System | |
| if (sender === 'system') { | |
| const spark = document.createElement('div'); | |
| spark.className = 'system-spark'; | |
| spark.innerHTML = '✨'; | |
| msgDiv.appendChild(spark); | |
| } | |
| const contentDiv = document.createElement('div'); | |
| contentDiv.className = 'message-content'; | |
| let renderText = highlightedText.replace('===DRAFT BEGIN===', '').replace('===DRAFT END===', ''); | |
| contentDiv.innerHTML = marked.parse(renderText); | |
| msgDiv.appendChild(contentDiv); | |
| // Action Button Detection (Proactive Agent Feature) | |
| if (sender === 'system') { | |
| const actionArea = document.createElement('div'); | |
| actionArea.classList.add('action-area'); | |
| let hasDraft = text.includes('===DRAFT BEGIN==='); | |
| if (hasDraft) { | |
| // Helper function to extract just the draft | |
| const extractDraft = (fullText) => { | |
| const startMarker = '===DRAFT BEGIN==='; | |
| const endMarker = '===DRAFT END==='; | |
| let startIndex = fullText.indexOf(startMarker); | |
| let endIndex = fullText.indexOf(endMarker); | |
| if (startIndex !== -1 && endIndex !== -1) { | |
| return fullText.substring(startIndex + startMarker.length, endIndex).trim(); | |
| } | |
| return fullText; // Fallback | |
| }; | |
| const draftText = extractDraft(text); | |
| currentDraftText = draftText; // Store globally for editing | |
| const copyBtn = createActionButton('copy', 'Copy Draft', () => { | |
| navigator.clipboard.writeText(draftText); | |
| alert('Draft copied to clipboard! (Satyameva Jayate)'); | |
| }); | |
| actionArea.appendChild(copyBtn); | |
| const docBtn = createActionButton('file-text', 'Download DOC', () => exportToDoc(draftText)); | |
| actionArea.appendChild(docBtn); | |
| const pdfBtn = createActionButton('download', 'Download PDF', () => exportToPdf(draftText)); | |
| actionArea.appendChild(pdfBtn); | |
| const editBtn = createActionButton('edit-3', 'Edit Draft', () => { | |
| if (lastCustomFields && lastFormValues) { | |
| addNoticeForm(lastCustomFields, lastFormValues); | |
| } else { | |
| addNoticeForm([], []); // Show form with just Additional Instructions | |
| } | |
| }); | |
| actionArea.appendChild(editBtn); | |
| } else { | |
| if (text.toLowerCase().includes('rti query') || text.toLowerCase().includes('rti act')) { | |
| const btn = createActionButton('scroll', 'Draft RTI Query', () => { | |
| userInput.value = "Draft an RTI query for this matter."; | |
| handleSendMessage(); | |
| }); | |
| actionArea.appendChild(btn); | |
| } | |
| if (text.toLowerCase().includes('legal notice')) { | |
| const btn = createActionButton('file-text', 'Prepare Legal Notice', () => { | |
| userInput.value = "Please draft a legal notice for this matter."; | |
| handleSendMessage(); | |
| }); | |
| actionArea.appendChild(btn); | |
| } | |
| } | |
| if (actionArea.children.length > 0) { | |
| msgDiv.appendChild(actionArea); | |
| } | |
| } | |
| const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| const timeSpan = document.createElement('span'); | |
| timeSpan.classList.add('message-time'); | |
| timeSpan.innerText = time; | |
| msgDiv.appendChild(timeSpan); | |
| chatMessages.appendChild(msgDiv); | |
| setTimeout(scrollToBottom, 50); // Small delay for smooth rendering | |
| lucide.createIcons(); | |
| } | |
| function scrollToBottom() { | |
| chatMessages.scrollTo({ | |
| top: chatMessages.scrollHeight, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| function exportToDoc(text) { | |
| const header = "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta charset='utf-8'><title>Legal Draft</title></head><body>"; | |
| const footer = "</body></html>"; | |
| const sourceHTML = header + marked.parse(text) + footer; | |
| const source = 'data:application/vnd.ms-word;charset=utf-8,' + encodeURIComponent(sourceHTML); | |
| const fileDownload = document.createElement("a"); | |
| document.body.appendChild(fileDownload); | |
| fileDownload.href = source; | |
| fileDownload.download = 'Nyaya_Legal_Draft.doc'; | |
| fileDownload.click(); | |
| document.body.removeChild(fileDownload); | |
| } | |
| function exportToPdf(text) { | |
| // Premium Print Mode for PDF | |
| const printWindow = window.open('', '_blank'); | |
| printWindow.document.write(` | |
| <html> | |
| <head> | |
| <title>Nyaya Legal Draft</title> | |
| <style> | |
| body { font-family: 'Times New Roman', serif; padding: 50px; line-height: 1.6; } | |
| h1, h2 { color: #4a0e0e; border-bottom: 2px solid #c5a059; } | |
| .watermark { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) rotate(-45deg); opacity: 0.1; font-size: 80px; z-index: -1; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="watermark">NYAYA AGENT</div> | |
| ${marked.parse(text)} | |
| <script>setTimeout(() => { window.print(); window.close(); }, 500);</script> | |
| </body> | |
| </html> | |
| `); | |
| printWindow.document.close(); | |
| } | |
| function createActionButton(icon, label, callback) { | |
| const btn = document.createElement('button'); | |
| btn.classList.add('action-btn'); | |
| btn.innerHTML = `<i data-lucide="${icon}"></i> <span>${label}</span>`; | |
| btn.onclick = callback; | |
| return btn; | |
| } | |
| function addNoticeForm(customFields = ["Sender Details (Name, Address)", "Receiver Details (Name, Address)", "Grievance / Cause of Action", "Relief / Demand"], previousValues = null) { | |
| const msgDiv = document.createElement('div'); | |
| msgDiv.className = `message system`; | |
| // Generate unique ID suffix for this specific form instance | |
| const uniqueId = Date.now(); | |
| // Store globally for edit draft | |
| lastCustomFields = customFields; | |
| let inputsHtml = customFields.map((field, index) => { | |
| const isLongText = field.toLowerCase().includes('grievance') || field.toLowerCase().includes('relief') || field.toLowerCase().includes('grounds') || field.toLowerCase().includes('facts'); | |
| const isDate = field.toLowerCase().includes('date'); | |
| let value = previousValues && previousValues[index] ? previousValues[index] : ""; | |
| value = value.replace(/"/g, '"'); | |
| if (isLongText) { | |
| return `<textarea id="notice-field-${index}-${uniqueId}" placeholder="${field}" class="form-input">${value}</textarea>`; | |
| } else if (isDate) { | |
| const today = new Date().toISOString().split('T')[0]; | |
| return ` | |
| <div style="margin-bottom: 0.5rem; color: rgba(255,255,255,0.7); font-size: 0.85rem;">${field}</div> | |
| <input type="date" id="notice-field-${index}-${uniqueId}" min="1947-01-01" max="${today}" class="form-input" value="${value}"> | |
| `; | |
| } else { | |
| return `<input type="text" id="notice-field-${index}-${uniqueId}" placeholder="${field}" class="form-input" value="${value}">`; | |
| } | |
| }).join(''); | |
| let additionalInstructionsHtml = previousValues ? ` | |
| <div style="margin-bottom: 0.5rem; color: rgba(255,255,255,0.7); font-size: 0.85rem; margin-top: 10px;">Additional Instructions / Changes</div> | |
| <textarea id="notice-additional-instructions-${uniqueId}" placeholder="e.g. Include a demand for 18% interest, or update the date" class="form-input" style="border-color: var(--judicial-gold); min-height: 50px;"></textarea> | |
| ` : ""; | |
| const formTitle = previousValues ? "Edit Draft" : "Prepare Draft"; | |
| const btnText = previousValues ? "Update Draft" : "Prepare Draft"; | |
| msgDiv.innerHTML = ` | |
| <div class="message-content"> | |
| <h3 style="color: var(--judicial-gold); margin-bottom: 15px; font-family: 'Playfair Display', serif; border-bottom: 1px solid rgba(197, 160, 89, 0.2); padding-bottom: 0.5rem;">${formTitle}</h3> | |
| <p style="margin-bottom: 10px;">Please provide the details below.</p> | |
| <div class="legal-form-container"> | |
| ${inputsHtml} | |
| ${additionalInstructionsHtml} | |
| <button class="gold-btn form-submit-btn" id="generate-notice-btn-${uniqueId}"><i data-lucide="file-text"></i> ${btnText}</button> | |
| </div> | |
| </div> | |
| `; | |
| chatMessages.appendChild(msgDiv); | |
| setTimeout(() => { | |
| const btn = document.getElementById(`generate-notice-btn-${uniqueId}`); | |
| if(btn) { | |
| btn.onclick = () => { | |
| let promptParts = []; | |
| let currentValues = []; | |
| customFields.forEach((field, index) => { | |
| const el = document.getElementById(`notice-field-${index}-${uniqueId}`); | |
| if (el && el.value.trim()) { | |
| promptParts.push(`**${field}:** ${el.value.trim()}`); | |
| } | |
| currentValues.push(el.value.trim()); | |
| }); | |
| // Save values globally for next edit | |
| lastFormValues = currentValues; | |
| const additionalEl = document.getElementById(`notice-additional-instructions-${uniqueId}`); | |
| if (additionalEl && additionalEl.value.trim()) { | |
| promptParts.push(`**Additional Instructions / Changes:** ${additionalEl.value.trim()}`); | |
| } | |
| btn.disabled = true; | |
| btn.innerHTML = `<i data-lucide="loader" class="spin"></i> Drafting...`; | |
| let combinedPrompt = "Please prepare the formal legal draft using the following details:\\n\\n" + promptParts.join('\\n\\n'); | |
| if (previousValues && currentDraftText) { | |
| combinedPrompt += "\\n\\n===EXISTING DRAFT TO MODIFY===\\n" + currentDraftText + "\\n===END EXISTING DRAFT===\\n\\nPlease apply the requested changes/details to the existing draft and return the FULL updated draft."; | |
| } | |
| userInput.value = combinedPrompt; | |
| handleSendMessage(); | |
| }; | |
| } | |
| lucide.createIcons(); | |
| }); | |
| setTimeout(() => { | |
| chatMessages.scrollTo({ | |
| top: chatMessages.scrollHeight, | |
| behavior: 'smooth' | |
| }); | |
| }, 50); | |
| lucide.createIcons(); | |
| } | |
| async function handleSendMessage() { | |
| if (isRequestInProgress) return; | |
| const text = userInput.value.trim(); | |
| if (!text) return; | |
| setLockdown(true); | |
| // Add user message to UI | |
| addMessage(text, 'user'); | |
| userInput.value = ''; | |
| // Show animated loading indicator | |
| const loadingId = 'loading-' + Date.now(); | |
| const loadingDiv = document.createElement('div'); | |
| loadingDiv.id = loadingId; | |
| loadingDiv.classList.add('message', 'system', 'glass', 'legal-loader'); | |
| loadingDiv.innerHTML = ` | |
| <div class="gavel-animate"><i data-lucide="gavel"></i></div> | |
| <div class="loader-text">Order in the Court... Analyzing</div> | |
| `; | |
| chatMessages.appendChild(loadingDiv); | |
| lucide.createIcons(); | |
| scrollToBottom(); | |
| try { | |
| const apiPayload = { | |
| message: text, | |
| persona: userPersona | |
| }; | |
| // Silently inject the draft text if the user is editing it | |
| if (text.includes("Please modify the draft") && currentDraftText) { | |
| apiPayload.message = text + `\n\n===CURRENT DRAFT===\n${currentDraftText}`; | |
| } | |
| const response = await fetch(`${API_BASE_URL}/chat`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(apiPayload) | |
| }); | |
| const data = await response.json(); | |
| // Remove loading indicator | |
| document.getElementById(loadingId).remove(); | |
| if (data.response) { | |
| addMessage(data.response, 'system'); | |
| } else if (data.detail) { | |
| addMessage(`Error: ${data.detail}`, 'system'); | |
| } | |
| } catch (error) { | |
| if (document.getElementById(loadingId)) document.getElementById(loadingId).remove(); | |
| addMessage(`Connection Error: Could not reach the Judicial Network. Ensure the server is running.`, 'system'); | |
| console.error('API Error:', error); | |
| } finally { | |
| setLockdown(false); | |
| } | |
| } | |
| sendBtn.addEventListener('click', handleSendMessage); | |
| userInput.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| handleSendMessage(); | |
| } | |
| }); | |
| addMessage("Greetings. I am **Nyaya Agent**. I am ready to assist with your legal research, drafting, or compliance queries.", "system"); | |
| // Sidebar Navigation | |
| const toolExamples = { | |
| search: "Search for landmark cases. e.g., 'Find Supreme Court judgments on Right to Privacy (Justice K.S. Puttaswamy case)'", | |
| notice: "Prepare a formal notice. e.g., 'Help me draft a legal notice for non-payment of rent by a tenant.'", | |
| rti: "Draft an RTI application. e.g., 'Draft an RTI to find out the status of road repairs in Ward 12.'", | |
| compliance: "Upload a legal document using the paperclip icon (📎) below for the app to analyze. e.g., 'Audit this privacy policy for compliance with DPDPA 2023.'", | |
| bridge: "Translate old criminal laws (IPC, CrPC) to the newly enacted Bharatiya Nyaya Sanhita (BNS). e.g., 'What is the new BNS law for cheating (formerly IPC 420)?'" | |
| }; | |
| document.querySelectorAll('.nav-item').forEach(item => { | |
| item.addEventListener('click', () => { | |
| if (isRequestInProgress) { | |
| alert('Judicial Lockdown Active: Please wait for the current analysis to complete.'); | |
| return; | |
| } | |
| document.querySelectorAll('.nav-item').forEach(btn => btn.classList.remove('active')); | |
| item.classList.add('active'); | |
| const tool = item.getAttribute('data-tool'); | |
| // Toggle attachment button visibility | |
| const attachBtn = document.getElementById('attach-btn'); | |
| if (attachBtn) { | |
| attachBtn.style.display = (tool === 'compliance') ? 'flex' : 'none'; | |
| } | |
| if (tool !== 'chat') { | |
| const example = toolExamples[tool]; | |
| const moduleName = tool === 'bridge' ? 'Law BRIDGE' : tool.toUpperCase(); | |
| addMessage(`Switched to **${moduleName}** module.\n\n**Example:** ${example}`, 'system'); | |
| } | |
| }); | |
| }); | |