nyaya-agent / frontend /script.js
SeriousSam07's picture
Fix RTI edit draft context loss bug
c6fb81e
Raw
History Blame Contribute Delete
21.3 kB
// 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, '&quot;');
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');
}
});
});