1068 lines
30 KiB
HTML
1068 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>AGC Document Chatbot</title>
|
|
<link
|
|
rel="icon"
|
|
type="image/png"
|
|
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA3klEQVRYhe2WQQ6AIAxEX2Jic+xReAJP4BF4DE/gCbyBJ7CJfkQNDJREmoEkpkm3FN60r50WjDFmSTBzznVSyjOABsAGYAOQ/wB0WcDMa2ZeALj/gJTySQhxBrAA6ACsAC4ANsELOOdc57Vundd66j0AzHhfJSJe1kcAhv/UuXvOXgJF4pxz6F/G/rKgUQQzN2VZKgC7EUeQe49SSqW1VkKhDgAAM7VVVb0BgJQSgH5D9hZ6Y9Y0DS3LonrvkVJaO+/Tle31PmBmBnAm5w3LsvjOOWXKILhGAABaa621lpGfWLABWAHsAP49xCwVZJqTdWYAAAAASUVORK5CYII="
|
|
/>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background-color: #f5f5f5;
|
|
}
|
|
.container {
|
|
background-color: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
.search-container {
|
|
margin-bottom: 20px;
|
|
}
|
|
input[type="text"],
|
|
select {
|
|
padding: 8px;
|
|
margin-right: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
width: 200px;
|
|
}
|
|
button {
|
|
padding: 8px 16px;
|
|
background-color: #4caf50;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
button:hover {
|
|
background-color: #45a049;
|
|
}
|
|
.document-card {
|
|
border: 1px solid #ddd;
|
|
padding: 15px;
|
|
margin: 10px 0;
|
|
border-radius: 8px;
|
|
background-color: white;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
.document-card:hover {
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
/* Document Header */
|
|
.document-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 15px;
|
|
border-bottom: 1px solid #eee;
|
|
padding-bottom: 10px;
|
|
}
|
|
.document-title {
|
|
margin: 0;
|
|
color: #2c3e50;
|
|
font-size: 1.1rem;
|
|
flex: 1;
|
|
}
|
|
.document-meta {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
.doc-type {
|
|
background-color: #3498db;
|
|
color: white;
|
|
padding: 3px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.8rem;
|
|
font-weight: bold;
|
|
}
|
|
.doc-date {
|
|
color: #7f8c8d;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Document Summary */
|
|
.document-summary {
|
|
margin-bottom: 15px;
|
|
}
|
|
.info-section {
|
|
margin-bottom: 12px;
|
|
padding: 10px;
|
|
background-color: #f8f9fa;
|
|
border-radius: 4px;
|
|
border-left: 3px solid #4caf50;
|
|
}
|
|
.info-section h4 {
|
|
margin: 0 0 8px 0;
|
|
color: #2c3e50;
|
|
font-size: 0.95rem;
|
|
}
|
|
.info-section p {
|
|
margin: 4px 0;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Status styling */
|
|
.status-selesai {
|
|
color: #27ae60;
|
|
font-weight: bold;
|
|
}
|
|
.status-pending {
|
|
color: #f39c12;
|
|
font-weight: bold;
|
|
}
|
|
.status-ongoing {
|
|
color: #3498db;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Lists */
|
|
.acts-list,
|
|
.persons-list {
|
|
margin: 8px 0 0 0;
|
|
padding-left: 0;
|
|
list-style: none;
|
|
}
|
|
.act-item,
|
|
.person-item {
|
|
background-color: #ecf0f1;
|
|
margin: 3px 0;
|
|
padding: 4px 8px;
|
|
border-radius: 3px;
|
|
font-size: 0.85rem;
|
|
border-left: 3px solid #9b59b6;
|
|
}
|
|
.person-item {
|
|
border-left-color: #e74c3c;
|
|
}
|
|
|
|
/* Allegations */
|
|
.allegation-item {
|
|
margin: 8px 0;
|
|
padding: 8px;
|
|
background-color: #fff5f5;
|
|
border-radius: 4px;
|
|
border-left: 3px solid #e74c3c;
|
|
}
|
|
.allegation-preview {
|
|
margin: 4px 0 0 0;
|
|
font-size: 0.85rem;
|
|
color: #555;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* Search Result Cards */
|
|
.search-result-card {
|
|
border: 1px solid #ddd;
|
|
padding: 18px;
|
|
margin: 15px 0;
|
|
border-radius: 8px;
|
|
background-color: white;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.result-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 15px;
|
|
border-bottom: 2px solid #3498db;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
/* Relevance Score */
|
|
.relevance-score {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
min-width: 120px;
|
|
}
|
|
.score-label {
|
|
font-size: 0.8rem;
|
|
color: #7f8c8d;
|
|
margin-bottom: 2px;
|
|
}
|
|
.score-value {
|
|
font-size: 1.1rem;
|
|
font-weight: bold;
|
|
color: #2c3e50;
|
|
margin-bottom: 4px;
|
|
}
|
|
.score-bar {
|
|
width: 100px;
|
|
height: 6px;
|
|
background-color: #ecf0f1;
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
.score-fill {
|
|
height: 100%;
|
|
background: linear-gradient(
|
|
90deg,
|
|
#e74c3c 0%,
|
|
#f39c12 50%,
|
|
#27ae60 100%
|
|
);
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
/* Buttons */
|
|
.btn-primary,
|
|
.btn-secondary {
|
|
padding: 8px 16px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9rem;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
.btn-primary {
|
|
background-color: #3498db;
|
|
color: white;
|
|
}
|
|
.btn-primary:hover {
|
|
background-color: #2980b9;
|
|
}
|
|
.btn-secondary {
|
|
background-color: #95a5a6;
|
|
color: white;
|
|
}
|
|
.btn-secondary:hover {
|
|
background-color: #7f8c8d;
|
|
}
|
|
|
|
/* Document Details */
|
|
.document-details,
|
|
.search-details {
|
|
margin-top: 15px;
|
|
padding: 15px;
|
|
background-color: #f8f9fa;
|
|
border-radius: 4px;
|
|
border: 1px solid #dee2e6;
|
|
}
|
|
.full-content pre {
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
font-size: 0.85rem;
|
|
line-height: 1.4;
|
|
color: #495057;
|
|
background-color: white;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
border: 1px solid #dee2e6;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Search Info */
|
|
.search-info {
|
|
margin-bottom: 20px;
|
|
padding: 15px;
|
|
background-color: #e8f5e8;
|
|
border-radius: 6px;
|
|
border-left: 4px solid #4caf50;
|
|
}
|
|
.search-info em {
|
|
color: #2c3e50;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* No results */
|
|
.no-results {
|
|
text-align: center;
|
|
color: #7f8c8d;
|
|
font-style: italic;
|
|
padding: 20px;
|
|
background-color: #f8f9fa;
|
|
border-radius: 4px;
|
|
border: 1px dashed #dee2e6;
|
|
}
|
|
|
|
/* Document Preview */
|
|
.document-preview {
|
|
margin-top: 10px;
|
|
padding: 10px;
|
|
background-color: #f8f9fa;
|
|
border-radius: 4px;
|
|
border: 1px solid #dee2e6;
|
|
}
|
|
.document-preview h4 {
|
|
margin: 0 0 8px 0;
|
|
font-size: 0.9rem;
|
|
color: #495057;
|
|
}
|
|
.document-preview p {
|
|
margin: 0;
|
|
font-size: 0.85rem;
|
|
color: #6c757d;
|
|
line-height: 1.4;
|
|
}
|
|
.search-results {
|
|
margin-top: 20px;
|
|
}
|
|
.chat-container {
|
|
margin-top: 20px;
|
|
border-top: 1px solid #ddd;
|
|
padding-top: 20px;
|
|
}
|
|
.loading {
|
|
display: none;
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
.error {
|
|
color: red;
|
|
margin: 10px 0;
|
|
}
|
|
.tabs {
|
|
margin-bottom: 20px;
|
|
}
|
|
.tab-button {
|
|
padding: 10px 20px;
|
|
margin-right: 5px;
|
|
background-color: #f0f0f0;
|
|
border: none;
|
|
border-radius: 4px 4px 0 0;
|
|
cursor: pointer;
|
|
}
|
|
.tab-button.active {
|
|
background-color: #4caf50;
|
|
color: white;
|
|
}
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
/* Chat Results */
|
|
.chat-result {
|
|
background-color: white;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
border: 1px solid #ddd;
|
|
}
|
|
|
|
.question-section,
|
|
.answer-section,
|
|
.related-docs-section {
|
|
margin-bottom: 20px;
|
|
padding: 15px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.question-section {
|
|
background-color: #e3f2fd;
|
|
border-left: 4px solid #2196f3;
|
|
}
|
|
|
|
.answer-section {
|
|
background-color: #f1f8e9;
|
|
border-left: 4px solid #4caf50;
|
|
}
|
|
|
|
.related-docs-section {
|
|
background-color: #fff3e0;
|
|
border-left: 4px solid #ff9800;
|
|
}
|
|
|
|
.question-section h4,
|
|
.answer-section h4,
|
|
.related-docs-section h4 {
|
|
margin: 0 0 10px 0;
|
|
color: #2c3e50;
|
|
font-size: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.question-text {
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
color: #34495e;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.answer-text {
|
|
font-size: 0.95rem;
|
|
line-height: 1.6;
|
|
color: #2c3e50;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.related-docs {
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.related-doc-item {
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
margin-bottom: 8px;
|
|
transition: box-shadow 0.2s ease;
|
|
}
|
|
|
|
.related-doc-item:hover {
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.related-doc-item .doc-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.related-doc-item .doc-title {
|
|
font-weight: bold;
|
|
color: #2c3e50;
|
|
font-size: 0.9rem;
|
|
flex: 1;
|
|
}
|
|
|
|
.related-doc-item .doc-relevance {
|
|
background-color: #34495e;
|
|
color: white;
|
|
padding: 2px 6px;
|
|
border-radius: 10px;
|
|
font-size: 0.75rem;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.related-doc-item .doc-preview {
|
|
font-size: 0.85rem;
|
|
color: #7f8c8d;
|
|
line-height: 1.4;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>AGC Document Chatbot</h1>
|
|
|
|
<!-- Tabs -->
|
|
<div class="tabs">
|
|
<button class="tab-button active" onclick="showTab('browse')">
|
|
Browse Documents
|
|
</button>
|
|
<button class="tab-button" onclick="showTab('search')">Search</button>
|
|
<button class="tab-button" onclick="showTab('chat')">Chat</button>
|
|
</div>
|
|
|
|
<!-- Browse Documents Tab -->
|
|
<div id="browse" class="tab-content active">
|
|
<div class="search-container">
|
|
<select id="docType">
|
|
<option value="">All Types</option>
|
|
</select>
|
|
<input
|
|
type="text"
|
|
id="titleFilter"
|
|
placeholder="Filter by title/content"
|
|
/>
|
|
<button onclick="listDocuments()">Filter</button>
|
|
</div>
|
|
<div id="documentsList"></div>
|
|
</div>
|
|
|
|
<!-- Search Tab -->
|
|
<div id="search" class="tab-content">
|
|
<div class="search-container">
|
|
<input
|
|
type="text"
|
|
id="searchQuery"
|
|
placeholder="Enter search terms"
|
|
/>
|
|
<button onclick="searchDocuments()">Search</button>
|
|
</div>
|
|
<div id="searchResults" class="search-results"></div>
|
|
</div>
|
|
|
|
<!-- Chat Tab -->
|
|
<div id="chat" class="tab-content">
|
|
<div class="search-container">
|
|
<input
|
|
type="text"
|
|
id="chatQuery"
|
|
placeholder="Ask about legal concepts"
|
|
/>
|
|
<button onclick="chatSearch()">Ask</button>
|
|
</div>
|
|
<div id="chatResults" class="chat-container"></div>
|
|
</div>
|
|
|
|
<div id="loading" class="loading">Loading...</div>
|
|
<div id="error" class="error"></div>
|
|
</div>
|
|
|
|
<script>
|
|
const API_URL = "http://localhost:8000";
|
|
|
|
// Add keyboard shortcuts
|
|
document.addEventListener("keydown", function (e) {
|
|
// Enter key in search input
|
|
if (e.key === "Enter") {
|
|
if (
|
|
document.getElementById("searchQuery") === document.activeElement
|
|
) {
|
|
searchDocuments();
|
|
} else if (
|
|
document.getElementById("chatQuery") === document.activeElement
|
|
) {
|
|
chatSearch();
|
|
} else if (
|
|
document.getElementById("titleFilter") === document.activeElement
|
|
) {
|
|
listDocuments();
|
|
}
|
|
}
|
|
|
|
// Alt + number to switch tabs
|
|
if (e.altKey) {
|
|
switch (e.key) {
|
|
case "1":
|
|
showTab("browse");
|
|
break;
|
|
case "2":
|
|
showTab("search");
|
|
break;
|
|
case "3":
|
|
showTab("chat");
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Enhanced error handling
|
|
async function fetchWithTimeout(url, options = {}, timeout = 5000) {
|
|
const controller = new AbortController();
|
|
const id = setTimeout(() => controller.abort(), timeout);
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
...options,
|
|
signal: controller.signal,
|
|
});
|
|
clearTimeout(id);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
clearTimeout(id);
|
|
if (error.name === "AbortError") {
|
|
throw new Error("Request timed out");
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Initialize document types with enhanced error handling
|
|
async function initializeDocTypes() {
|
|
try {
|
|
const response = await fetchWithTimeout(`${API_URL}/document-types`);
|
|
const data = await response.json();
|
|
const select = document.getElementById("docType");
|
|
data.document_types.forEach((type) => {
|
|
const option = document.createElement("option");
|
|
option.value = type;
|
|
option.textContent = type;
|
|
select.appendChild(option);
|
|
});
|
|
} catch (error) {
|
|
showError(`Error loading document types: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Show/hide tabs
|
|
function showTab(tabId) {
|
|
document.querySelectorAll(".tab-content").forEach((tab) => {
|
|
tab.classList.remove("active");
|
|
});
|
|
document.querySelectorAll(".tab-button").forEach((button) => {
|
|
button.classList.remove("active");
|
|
});
|
|
document.getElementById(tabId).classList.add("active");
|
|
document
|
|
.querySelector(`button[onclick="showTab('${tabId}')"]`)
|
|
.classList.add("active");
|
|
}
|
|
|
|
// List documents
|
|
async function listDocuments() {
|
|
showLoading();
|
|
try {
|
|
const docType = document.getElementById("docType").value;
|
|
const titleFilter = document.getElementById("titleFilter").value;
|
|
|
|
const params = new URLSearchParams();
|
|
if (docType) params.append("doc_type", docType);
|
|
if (titleFilter) params.append("title_filter", titleFilter);
|
|
|
|
const response = await fetchWithTimeout(
|
|
`${API_URL}/documents?${params}`
|
|
);
|
|
const documents = await response.json();
|
|
|
|
if (documents.length === 0) {
|
|
showError("No documents found matching the criteria");
|
|
} else {
|
|
displayDocuments(documents);
|
|
}
|
|
} catch (error) {
|
|
showError(`Error loading documents: ${error.message}`);
|
|
}
|
|
hideLoading();
|
|
}
|
|
|
|
// Display documents
|
|
function displayDocuments(documents) {
|
|
const container = document.getElementById("documentsList");
|
|
container.innerHTML = "";
|
|
|
|
if (documents.length === 0) {
|
|
container.innerHTML =
|
|
'<p class="no-results">No documents found matching the criteria.</p>';
|
|
return;
|
|
}
|
|
|
|
documents.forEach((doc) => {
|
|
const card = document.createElement("div");
|
|
card.className = "document-card";
|
|
|
|
// Extract key information from content
|
|
const extractedData = extractLegalInfo(doc.content);
|
|
|
|
card.innerHTML = `
|
|
<div class="document-header">
|
|
<h3 class="document-title">${doc.title}</h3>
|
|
<div class="document-meta">
|
|
<span class="doc-type">${doc.doc_type}</span>
|
|
<span class="doc-date">${doc.created_at || "No date"}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="document-body">
|
|
${generateDocumentSummary(extractedData)}
|
|
|
|
<div class="document-preview">
|
|
<h4>Content Preview:</h4>
|
|
<p>${doc.content.substring(0, 200)}...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="document-footer">
|
|
<button class="btn-secondary" onclick="toggleDocumentDetails('${
|
|
doc.id
|
|
}')">
|
|
View Full Details
|
|
</button>
|
|
</div>
|
|
|
|
<div id="details-${
|
|
doc.id
|
|
}" class="document-details" style="display: none;">
|
|
<div class="full-content">
|
|
<h4>Full Document Content:</h4>
|
|
<pre>${doc.content}</pre>
|
|
</div>
|
|
</div>
|
|
`;
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// Extract legal information from content
|
|
function extractLegalInfo(content) {
|
|
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 = [];
|
|
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(),
|
|
});
|
|
}
|
|
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 = '<div class="document-summary">';
|
|
|
|
// Case information
|
|
if (data.caseNumber || data.fileNumber) {
|
|
summary += '<div class="info-section">';
|
|
summary += "<h4>Case Information:</h4>";
|
|
if (data.caseNumber)
|
|
summary += `<p><strong>Case:</strong> ${data.caseNumber}</p>`;
|
|
if (data.fileNumber)
|
|
summary += `<p><strong>File:</strong> ${data.fileNumber}</p>`;
|
|
if (data.status)
|
|
summary += `<p><strong>Status:</strong> <span class="status-${data.status.toLowerCase()}">${
|
|
data.status
|
|
}</span></p>`;
|
|
summary += "</div>";
|
|
}
|
|
|
|
// Legal acts
|
|
if (data.acts && data.acts.length > 0) {
|
|
summary += '<div class="info-section">';
|
|
summary += "<h4>Legal References:</h4>";
|
|
summary += '<ul class="acts-list">';
|
|
data.acts.forEach((act) => {
|
|
summary += `<li class="act-item">${act}</li>`;
|
|
});
|
|
summary += "</ul></div>";
|
|
}
|
|
|
|
// Persons involved
|
|
if (data.persons && data.persons.length > 0) {
|
|
summary += '<div class="info-section">';
|
|
summary += "<h4>Persons Involved:</h4>";
|
|
summary += '<ul class="persons-list">';
|
|
data.persons.forEach((person) => {
|
|
summary += `<li class="person-item">${person.name}</li>`;
|
|
});
|
|
summary += "</ul></div>";
|
|
}
|
|
|
|
// Allegations count
|
|
if (data.allegations && data.allegations.length > 0) {
|
|
summary += '<div class="info-section">';
|
|
summary += `<h4>Allegations: (${data.allegations.length})</h4>`;
|
|
data.allegations.forEach((allegation, index) => {
|
|
summary += `<div class="allegation-item">
|
|
<strong>Allegation ${allegation.number}:</strong>
|
|
<p class="allegation-preview">${allegation.details}</p>
|
|
</div>`;
|
|
});
|
|
summary += "</div>";
|
|
}
|
|
|
|
summary += "</div>";
|
|
return summary;
|
|
}
|
|
|
|
// Toggle functions for expanding details
|
|
function toggleDocumentDetails(docId) {
|
|
const details = document.getElementById(`details-${docId}`);
|
|
if (details.style.display === "none") {
|
|
details.style.display = "block";
|
|
} else {
|
|
details.style.display = "none";
|
|
}
|
|
}
|
|
|
|
// Search documents
|
|
async function searchDocuments() {
|
|
const query = document.getElementById("searchQuery").value.trim();
|
|
if (!query) {
|
|
showError("Please enter a search query");
|
|
return;
|
|
}
|
|
|
|
showLoading();
|
|
try {
|
|
const response = await fetchWithTimeout(`${API_URL}/search`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
query: query,
|
|
profile_search: false,
|
|
}),
|
|
});
|
|
const result = await response.json();
|
|
displaySearchResults(result);
|
|
} catch (error) {
|
|
showError(`Error performing search: ${error.message}`);
|
|
}
|
|
hideLoading();
|
|
}
|
|
|
|
// Display search results with enhanced formatting
|
|
function displaySearchResults(result) {
|
|
const container = document.getElementById("searchResults");
|
|
container.innerHTML = `
|
|
<div class="search-info">
|
|
<p><strong>Enhanced Query:</strong> <em>${result.enhanced_query}</em></p>
|
|
<h3>Results: (${result.documents.length} found)</h3>
|
|
</div>
|
|
`;
|
|
|
|
if (result.documents.length === 0) {
|
|
container.innerHTML +=
|
|
'<p class="no-results">No matching documents found.</p>';
|
|
return;
|
|
}
|
|
|
|
result.documents.forEach((doc, index) => {
|
|
const card = document.createElement("div");
|
|
card.className = "search-result-card";
|
|
|
|
const extractedData = extractLegalInfo(
|
|
doc.content || doc.content_preview
|
|
);
|
|
|
|
card.innerHTML = `
|
|
<div class="result-header">
|
|
<h3>${doc.title}</h3>
|
|
<div class="relevance-score">
|
|
<span class="score-label">Relevance:</span>
|
|
<span class="score-value">${doc.similarity.toFixed(3)}</span>
|
|
<div class="score-bar">
|
|
<div class="score-fill" style="width: ${(
|
|
doc.similarity * 100
|
|
).toFixed(0)}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="result-body">
|
|
${generateDocumentSummary(extractedData)}
|
|
</div>
|
|
|
|
<div class="result-footer">
|
|
<button class="btn-primary" onclick="toggleSearchDetails('${index}')">
|
|
View Full Content
|
|
</button>
|
|
</div>
|
|
|
|
<div id="search-details-${index}" class="search-details" style="display: none;">
|
|
<div class="full-content">
|
|
<h4>Full Content:</h4>
|
|
<pre>${doc.content_preview}</pre>
|
|
</div>
|
|
</div>
|
|
`;
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// Toggle functions for expanding details
|
|
function toggleSearchDetails(index) {
|
|
const details = document.getElementById(`search-details-${index}`);
|
|
if (details.style.display === "none") {
|
|
details.style.display = "block";
|
|
} else {
|
|
details.style.display = "none";
|
|
}
|
|
}
|
|
|
|
// Chat search
|
|
async function chatSearch() {
|
|
const query = document.getElementById("chatQuery").value.trim();
|
|
if (!query) {
|
|
showError("Please enter a question");
|
|
return;
|
|
}
|
|
|
|
showLoading();
|
|
try {
|
|
const response = await fetchWithTimeout(`${API_URL}/search`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
query: query,
|
|
profile_search: false,
|
|
}),
|
|
});
|
|
const result = await response.json();
|
|
displayChatResults(result);
|
|
} catch (error) {
|
|
showError(`Error in chat: ${error.message}`);
|
|
}
|
|
hideLoading();
|
|
}
|
|
|
|
// Display chat results
|
|
function displayChatResults(result) {
|
|
const container = document.getElementById("chatResults");
|
|
container.innerHTML = `
|
|
<div class="chat-result">
|
|
<div class="question-section">
|
|
<h4><i class="icon-question">❓</i> Question:</h4>
|
|
<p class="question-text">${result.query}</p>
|
|
</div>
|
|
|
|
<div class="answer-section">
|
|
<h4><i class="icon-answer">💡</i> Answer:</h4>
|
|
<div class="answer-text">${result.answer}</div>
|
|
</div>
|
|
|
|
${
|
|
result.documents && result.documents.length > 0
|
|
? `
|
|
<div class="related-docs-section">
|
|
<h4><i class="icon-docs">📚</i> Related Documents (${
|
|
result.documents.length
|
|
}):</h4>
|
|
<div class="related-docs">
|
|
${result.documents
|
|
.slice(0, 3)
|
|
.map(
|
|
(doc, index) => `
|
|
<div class="related-doc-item">
|
|
<div class="doc-header">
|
|
<span class="doc-title">${doc.title}</span>
|
|
<span class="doc-relevance">${doc.similarity.toFixed(
|
|
3
|
|
)}</span>
|
|
</div>
|
|
<div class="doc-preview">${doc.content_preview.substring(
|
|
0,
|
|
150
|
|
)}...</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("")}
|
|
</div>
|
|
|
|
${
|
|
result.documents.length > 3
|
|
? `
|
|
<button class="btn-secondary" onclick="toggleAllRelatedDocs()">
|
|
View All ${result.documents.length} Related Documents
|
|
</button>
|
|
|
|
<div id="all-related-docs" style="display: none;">
|
|
${result.documents
|
|
.slice(3)
|
|
.map(
|
|
(doc, index) => `
|
|
<div class="related-doc-item">
|
|
<div class="doc-header">
|
|
<span class="doc-title">${doc.title}</span>
|
|
<span class="doc-relevance">${doc.similarity.toFixed(
|
|
3
|
|
)}</span>
|
|
</div>
|
|
<div class="doc-preview">${doc.content_preview.substring(
|
|
0,
|
|
150
|
|
)}...</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("")}
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function toggleAllRelatedDocs() {
|
|
const allDocs = document.getElementById("all-related-docs");
|
|
if (allDocs.style.display === "none") {
|
|
allDocs.style.display = "block";
|
|
} else {
|
|
allDocs.style.display = "none";
|
|
}
|
|
}
|
|
|
|
// Utility functions
|
|
function showLoading() {
|
|
document.getElementById("loading").style.display = "block";
|
|
document.getElementById("error").style.display = "none";
|
|
}
|
|
|
|
function hideLoading() {
|
|
document.getElementById("loading").style.display = "none";
|
|
}
|
|
|
|
function showError(message) {
|
|
const errorDiv = document.getElementById("error");
|
|
errorDiv.textContent = message;
|
|
errorDiv.style.display = "block";
|
|
}
|
|
|
|
// Initialize the page
|
|
initializeDocTypes();
|
|
listDocuments();
|
|
</script>
|
|
</body>
|
|
</html>
|