Spaces:
Running
Running
Update index.html
Browse files- index.html +172 -114
index.html
CHANGED
|
@@ -27,15 +27,13 @@
|
|
| 27 |
box-sizing: border-box;
|
| 28 |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 29 |
}
|
| 30 |
-
|
| 31 |
-
/* FIX 1: body fills viewport exactly, no overflow */
|
| 32 |
body {
|
| 33 |
background-color: var(--bg-light);
|
| 34 |
color: var(--dark);
|
| 35 |
-
height: 100vh;
|
| 36 |
display: flex;
|
| 37 |
flex-direction: column;
|
| 38 |
-
overflow: hidden;
|
| 39 |
}
|
| 40 |
|
| 41 |
.container {
|
|
@@ -51,7 +49,8 @@
|
|
| 51 |
color: white;
|
| 52 |
padding: 1rem 0;
|
| 53 |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| 54 |
-
|
|
|
|
| 55 |
z-index: 100;
|
| 56 |
}
|
| 57 |
|
|
@@ -93,29 +92,12 @@
|
|
| 93 |
gap: 5px;
|
| 94 |
}
|
| 95 |
|
| 96 |
-
/* FIX 3: main area fills remaining height, scrolls internally */
|
| 97 |
-
.main-wrapper {
|
| 98 |
-
flex: 1;
|
| 99 |
-
overflow: hidden;
|
| 100 |
-
display: flex;
|
| 101 |
-
flex-direction: column;
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
.main-wrapper > .container {
|
| 105 |
-
flex: 1;
|
| 106 |
-
overflow: hidden;
|
| 107 |
-
display: flex;
|
| 108 |
-
flex-direction: column;
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
/* Main Layout */
|
| 112 |
.main-layout {
|
| 113 |
display: flex;
|
| 114 |
flex: 1;
|
| 115 |
gap: 20px;
|
| 116 |
padding: 20px 0;
|
| 117 |
-
overflow: hidden;
|
| 118 |
-
min-height: 0; /* critical — allows flex children to shrink below content size */
|
| 119 |
}
|
| 120 |
|
| 121 |
/* Chat Container */
|
|
@@ -127,7 +109,6 @@
|
|
| 127 |
display: flex;
|
| 128 |
flex-direction: column;
|
| 129 |
overflow: hidden;
|
| 130 |
-
min-height: 0;
|
| 131 |
}
|
| 132 |
|
| 133 |
.chat-header {
|
|
@@ -136,7 +117,6 @@
|
|
| 136 |
display: flex;
|
| 137 |
justify-content: space-between;
|
| 138 |
align-items: center;
|
| 139 |
-
flex-shrink: 0;
|
| 140 |
}
|
| 141 |
|
| 142 |
.chat-header h2 {
|
|
@@ -157,7 +137,6 @@
|
|
| 157 |
border-radius: 50%;
|
| 158 |
}
|
| 159 |
|
| 160 |
-
/* Chat history scrolls, input stays pinned */
|
| 161 |
.chat-history {
|
| 162 |
flex: 1;
|
| 163 |
padding: 20px;
|
|
@@ -166,7 +145,6 @@
|
|
| 166 |
flex-direction: column;
|
| 167 |
gap: 20px;
|
| 168 |
background-color: #fafafa;
|
| 169 |
-
min-height: 0;
|
| 170 |
}
|
| 171 |
|
| 172 |
.message {
|
|
@@ -241,12 +219,11 @@
|
|
| 241 |
font-size: 0.85rem;
|
| 242 |
}
|
| 243 |
|
| 244 |
-
/* Input Area
|
| 245 |
.input-container {
|
| 246 |
padding: 15px 20px;
|
| 247 |
border-top: 1px solid var(--border);
|
| 248 |
background: white;
|
| 249 |
-
flex-shrink: 0;
|
| 250 |
}
|
| 251 |
|
| 252 |
.input-area {
|
|
@@ -298,11 +275,13 @@
|
|
| 298 |
/* Sidebar */
|
| 299 |
.sidebar {
|
| 300 |
width: 300px;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
display: flex;
|
| 302 |
flex-direction: column;
|
| 303 |
gap: 20px;
|
| 304 |
-
overflow-y: auto;
|
| 305 |
-
min-height: 0;
|
| 306 |
}
|
| 307 |
|
| 308 |
.card {
|
|
@@ -310,7 +289,6 @@
|
|
| 310 |
border-radius: 8px;
|
| 311 |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
| 312 |
overflow: hidden;
|
| 313 |
-
flex-shrink: 0;
|
| 314 |
}
|
| 315 |
|
| 316 |
.card-header {
|
|
@@ -369,9 +347,17 @@
|
|
| 369 |
animation: typing 1.4s infinite ease-in-out;
|
| 370 |
}
|
| 371 |
|
| 372 |
-
.typing-dot:nth-child(1) {
|
| 373 |
-
|
| 374 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
@keyframes typing {
|
| 377 |
0%, 60%, 100% { transform: translateY(0); }
|
|
@@ -379,16 +365,25 @@
|
|
| 379 |
}
|
| 380 |
|
| 381 |
/* Status indicators */
|
| 382 |
-
.connected
|
| 383 |
-
|
| 384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
/* Footer */
|
| 387 |
footer {
|
| 388 |
background: var(--dark);
|
| 389 |
color: white;
|
| 390 |
-
padding:
|
| 391 |
-
|
| 392 |
}
|
| 393 |
|
| 394 |
.footer-content {
|
|
@@ -405,16 +400,17 @@
|
|
| 405 |
|
| 406 |
/* Responsive Design */
|
| 407 |
@media (max-width: 900px) {
|
| 408 |
-
body { overflow: auto; height: auto; }
|
| 409 |
-
.main-wrapper { overflow: visible; }
|
| 410 |
-
.main-wrapper > .container { overflow: visible; }
|
| 411 |
.main-layout {
|
| 412 |
flex-direction: column;
|
| 413 |
-
overflow: visible;
|
| 414 |
}
|
| 415 |
-
|
| 416 |
-
.
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
}
|
| 419 |
|
| 420 |
@media (max-width: 600px) {
|
|
@@ -423,13 +419,20 @@
|
|
| 423 |
gap: 10px;
|
| 424 |
align-items: flex-start;
|
| 425 |
}
|
|
|
|
| 426 |
.footer-content {
|
| 427 |
flex-direction: column;
|
| 428 |
gap: 15px;
|
| 429 |
align-items: flex-start;
|
| 430 |
}
|
| 431 |
-
|
| 432 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
}
|
| 434 |
</style>
|
| 435 |
</head>
|
|
@@ -450,69 +453,65 @@
|
|
| 450 |
</div>
|
| 451 |
</div>
|
| 452 |
</header>
|
| 453 |
-
|
| 454 |
-
<
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
<div class="
|
| 458 |
-
<
|
| 459 |
-
|
| 460 |
-
<div class="
|
| 461 |
-
<
|
| 462 |
-
<
|
| 463 |
-
<div class="status-dot" id="statusDot"></div>
|
| 464 |
-
<span id="connectionStatus">Connecting...</span>
|
| 465 |
-
</div>
|
| 466 |
</div>
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
</
|
| 474 |
-
<div class="message-content">
|
| 475 |
-
<p>Welcome to the Irish Legal AI Assistant! I'm here to help with your legal questions regarding Irish law.</p>
|
| 476 |
-
<p>Please ask your question in the box below. I'll provide a concise answer with relevant legal sources.</p>
|
| 477 |
-
</div>
|
| 478 |
</div>
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
<div class="input-container">
|
| 483 |
-
<div class="input-area">
|
| 484 |
-
<textarea id="userInput" placeholder="Ask a question about Irish law..." required></textarea>
|
| 485 |
-
<button id="sendButton">
|
| 486 |
-
<i class="fas fa-paper-plane"></i> Send
|
| 487 |
-
</button>
|
| 488 |
</div>
|
| 489 |
</div>
|
| 490 |
</div>
|
| 491 |
|
| 492 |
-
<
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
<
|
| 496 |
-
<i class="fas fa-
|
| 497 |
-
</
|
| 498 |
-
<div class="card-body" id="historyList">
|
| 499 |
-
<p style="color: var(--gray); text-align: center;">No history yet</p>
|
| 500 |
-
</div>
|
| 501 |
</div>
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
</div>
|
| 517 |
</div>
|
| 518 |
</div>
|
|
@@ -538,17 +537,20 @@
|
|
| 538 |
const connectionStatus = document.getElementById('connectionStatus');
|
| 539 |
const statusDot = document.getElementById('statusDot');
|
| 540 |
|
|
|
|
| 541 |
let sessionId = null;
|
| 542 |
let sessionHistory = [];
|
| 543 |
let sessionCheckInterval;
|
| 544 |
-
const SESSION_CHECK_INTERVAL = 1000;
|
| 545 |
|
|
|
|
| 546 |
function initializeSession() {
|
| 547 |
updateConnectionStatus('connecting');
|
| 548 |
checkSessionStatus();
|
| 549 |
sessionCheckInterval = setInterval(checkSessionStatus, SESSION_CHECK_INTERVAL);
|
| 550 |
}
|
| 551 |
|
|
|
|
| 552 |
function updateConnectionStatus(status) {
|
| 553 |
switch(status) {
|
| 554 |
case 'connected':
|
|
@@ -569,17 +571,23 @@
|
|
| 569 |
}
|
| 570 |
}
|
| 571 |
|
|
|
|
| 572 |
async function checkSessionStatus() {
|
| 573 |
try {
|
| 574 |
const response = await fetch('session/status', {
|
| 575 |
method: 'GET',
|
| 576 |
credentials: 'include'
|
| 577 |
});
|
|
|
|
| 578 |
if (!response.ok) throw new Error('Status check failed');
|
|
|
|
| 579 |
const data = await response.json();
|
| 580 |
updateConnectionStatus('connected');
|
| 581 |
updateSessionDisplay(data);
|
| 582 |
-
|
|
|
|
|
|
|
|
|
|
| 583 |
} catch (error) {
|
| 584 |
console.error('Session status check error:', error);
|
| 585 |
updateConnectionStatus('disconnected');
|
|
@@ -587,6 +595,7 @@
|
|
| 587 |
}
|
| 588 |
}
|
| 589 |
|
|
|
|
| 590 |
function updateSessionDisplay(sessionData) {
|
| 591 |
switch (sessionData.status) {
|
| 592 |
case 'new':
|
|
@@ -596,6 +605,7 @@
|
|
| 596 |
sessionHistory = [];
|
| 597 |
updateSessionHistory([]);
|
| 598 |
break;
|
|
|
|
| 599 |
case 'expired':
|
| 600 |
sessionTimerElement.textContent = "Session Expired";
|
| 601 |
sessionTimerElement.classList.remove('expiring-soon');
|
|
@@ -603,16 +613,22 @@
|
|
| 603 |
sessionHistory = [];
|
| 604 |
updateSessionHistory([]);
|
| 605 |
break;
|
|
|
|
| 606 |
case 'active':
|
| 607 |
sessionId = sessionData.session_id;
|
| 608 |
const minutes = Math.floor(sessionData.ttl / 60);
|
| 609 |
const seconds = sessionData.ttl % 60;
|
| 610 |
-
|
|
|
|
|
|
|
| 611 |
sessionTimerElement.classList.add('expiring-soon');
|
| 612 |
} else {
|
| 613 |
sessionTimerElement.classList.remove('expiring-soon');
|
| 614 |
}
|
|
|
|
| 615 |
sessionTimerElement.textContent = `Expires in ${minutes}:${seconds.toString().padStart(2, '0')}`;
|
|
|
|
|
|
|
| 616 |
if (sessionData.history_count !== sessionHistory.length) {
|
| 617 |
fetchSessionHistory();
|
| 618 |
}
|
|
@@ -620,13 +636,16 @@
|
|
| 620 |
}
|
| 621 |
}
|
| 622 |
|
|
|
|
| 623 |
async function fetchSessionHistory() {
|
| 624 |
try {
|
| 625 |
const response = await fetch('session/history', {
|
| 626 |
method: 'GET',
|
| 627 |
credentials: 'include'
|
| 628 |
});
|
|
|
|
| 629 |
if (!response.ok) throw new Error('History fetch failed');
|
|
|
|
| 630 |
const data = await response.json();
|
| 631 |
sessionHistory = data.history || [];
|
| 632 |
updateSessionHistory(sessionHistory);
|
|
@@ -636,48 +655,68 @@
|
|
| 636 |
}
|
| 637 |
}
|
| 638 |
|
|
|
|
| 639 |
function updateSessionHistory(history) {
|
| 640 |
historyList.innerHTML = '';
|
|
|
|
| 641 |
if (history.length === 0) {
|
| 642 |
historyList.innerHTML = '<p style="color: var(--gray); text-align: center;">No history yet</p>';
|
| 643 |
return;
|
| 644 |
}
|
| 645 |
-
|
|
|
|
| 646 |
const historyItem = document.createElement('div');
|
| 647 |
historyItem.className = 'history-item';
|
| 648 |
-
historyItem.innerHTML = `
|
| 649 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 650 |
historyList.appendChild(historyItem);
|
| 651 |
});
|
| 652 |
}
|
| 653 |
|
|
|
|
| 654 |
function scrollToMessage(query) {
|
| 655 |
const messages = document.querySelectorAll('.message');
|
| 656 |
for (let msg of messages) {
|
| 657 |
if (msg.textContent.includes(query)) {
|
| 658 |
msg.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
|
|
| 659 |
msg.style.boxShadow = '0 0 0 2px var(--accent)';
|
| 660 |
-
setTimeout(() => {
|
|
|
|
|
|
|
| 661 |
break;
|
| 662 |
}
|
| 663 |
}
|
| 664 |
}
|
| 665 |
|
|
|
|
| 666 |
function truncateText(text, maxLength) {
|
| 667 |
if (!text) return "";
|
| 668 |
return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
|
| 669 |
}
|
| 670 |
|
|
|
|
| 671 |
function handleSessionExpiry() {
|
| 672 |
sessionId = null;
|
| 673 |
sessionHistory = [];
|
|
|
|
|
|
|
| 674 |
while (chatHistory.children.length > 1) {
|
| 675 |
chatHistory.removeChild(chatHistory.lastChild);
|
| 676 |
}
|
| 677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
updateSessionHistory([]);
|
| 679 |
}
|
| 680 |
|
|
|
|
| 681 |
sendButton.addEventListener('click', sendMessage);
|
| 682 |
userInput.addEventListener('keypress', function(e) {
|
| 683 |
if (e.key === 'Enter' && !e.shiftKey) {
|
|
@@ -690,10 +729,12 @@
|
|
| 690 |
const question = userInput.value.trim();
|
| 691 |
if (!question) return;
|
| 692 |
|
|
|
|
| 693 |
addMessage(question, 'user');
|
| 694 |
userInput.value = '';
|
| 695 |
sendButton.disabled = true;
|
| 696 |
|
|
|
|
| 697 |
const typingIndicator = document.createElement('div');
|
| 698 |
typingIndicator.className = 'typing-indicator';
|
| 699 |
typingIndicator.innerHTML = `
|
|
@@ -705,23 +746,35 @@
|
|
| 705 |
chatHistory.scrollTop = chatHistory.scrollHeight;
|
| 706 |
|
| 707 |
try {
|
|
|
|
| 708 |
const response = await fetch('query', {
|
| 709 |
method: 'POST',
|
| 710 |
-
headers: {
|
|
|
|
|
|
|
| 711 |
body: JSON.stringify({ query: question }),
|
| 712 |
credentials: 'include'
|
| 713 |
});
|
| 714 |
|
| 715 |
-
if (!response.ok)
|
|
|
|
|
|
|
| 716 |
|
| 717 |
const data = await response.json();
|
|
|
|
|
|
|
| 718 |
chatHistory.removeChild(typingIndicator);
|
|
|
|
|
|
|
| 719 |
addMessage(data.answer, 'ai', data.sources);
|
| 720 |
|
|
|
|
| 721 |
if (data.session_id) {
|
| 722 |
sessionId = data.session_id;
|
|
|
|
| 723 |
fetchSessionHistory();
|
| 724 |
}
|
|
|
|
| 725 |
} catch (error) {
|
| 726 |
console.error('Error sending message:', error);
|
| 727 |
chatHistory.removeChild(typingIndicator);
|
|
@@ -734,8 +787,10 @@
|
|
| 734 |
function addMessage(content, sender, sources = []) {
|
| 735 |
const messageDiv = document.createElement('div');
|
| 736 |
messageDiv.className = `message ${sender}-message`;
|
|
|
|
| 737 |
const now = new Date();
|
| 738 |
const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
|
|
| 739 |
messageDiv.innerHTML = `
|
| 740 |
<div class="message-header">
|
| 741 |
<span><i class="${sender === 'ai' ? 'fas fa-robot' : 'fas fa-user'}"></i> ${sender === 'ai' ? 'Legal Assistant' : 'You'}</span>
|
|
@@ -751,16 +806,19 @@
|
|
| 751 |
` : ''}
|
| 752 |
</div>
|
| 753 |
`;
|
|
|
|
| 754 |
chatHistory.appendChild(messageDiv);
|
| 755 |
chatHistory.scrollTop = chatHistory.scrollHeight;
|
| 756 |
}
|
| 757 |
|
| 758 |
function formatMessageContent(content) {
|
| 759 |
if (!content) return "";
|
|
|
|
| 760 |
const paragraphs = content.split('\n\n');
|
| 761 |
return paragraphs.map(p => `<p>${p}</p>`).join('');
|
| 762 |
}
|
| 763 |
|
|
|
|
| 764 |
initializeSession();
|
| 765 |
});
|
| 766 |
</script>
|
|
|
|
| 27 |
box-sizing: border-box;
|
| 28 |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 29 |
}
|
| 30 |
+
|
|
|
|
| 31 |
body {
|
| 32 |
background-color: var(--bg-light);
|
| 33 |
color: var(--dark);
|
| 34 |
+
min-height: 100vh;
|
| 35 |
display: flex;
|
| 36 |
flex-direction: column;
|
|
|
|
| 37 |
}
|
| 38 |
|
| 39 |
.container {
|
|
|
|
| 49 |
color: white;
|
| 50 |
padding: 1rem 0;
|
| 51 |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| 52 |
+
position: sticky;
|
| 53 |
+
top: 0;
|
| 54 |
z-index: 100;
|
| 55 |
}
|
| 56 |
|
|
|
|
| 92 |
gap: 5px;
|
| 93 |
}
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
/* Main Layout */
|
| 96 |
.main-layout {
|
| 97 |
display: flex;
|
| 98 |
flex: 1;
|
| 99 |
gap: 20px;
|
| 100 |
padding: 20px 0;
|
|
|
|
|
|
|
| 101 |
}
|
| 102 |
|
| 103 |
/* Chat Container */
|
|
|
|
| 109 |
display: flex;
|
| 110 |
flex-direction: column;
|
| 111 |
overflow: hidden;
|
|
|
|
| 112 |
}
|
| 113 |
|
| 114 |
.chat-header {
|
|
|
|
| 117 |
display: flex;
|
| 118 |
justify-content: space-between;
|
| 119 |
align-items: center;
|
|
|
|
| 120 |
}
|
| 121 |
|
| 122 |
.chat-header h2 {
|
|
|
|
| 137 |
border-radius: 50%;
|
| 138 |
}
|
| 139 |
|
|
|
|
| 140 |
.chat-history {
|
| 141 |
flex: 1;
|
| 142 |
padding: 20px;
|
|
|
|
| 145 |
flex-direction: column;
|
| 146 |
gap: 20px;
|
| 147 |
background-color: #fafafa;
|
|
|
|
| 148 |
}
|
| 149 |
|
| 150 |
.message {
|
|
|
|
| 219 |
font-size: 0.85rem;
|
| 220 |
}
|
| 221 |
|
| 222 |
+
/* Input Area */
|
| 223 |
.input-container {
|
| 224 |
padding: 15px 20px;
|
| 225 |
border-top: 1px solid var(--border);
|
| 226 |
background: white;
|
|
|
|
| 227 |
}
|
| 228 |
|
| 229 |
.input-area {
|
|
|
|
| 275 |
/* Sidebar */
|
| 276 |
.sidebar {
|
| 277 |
width: 300px;
|
| 278 |
+
background: white;
|
| 279 |
+
border-radius: 10px;
|
| 280 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
| 281 |
+
padding: 20px;
|
| 282 |
display: flex;
|
| 283 |
flex-direction: column;
|
| 284 |
gap: 20px;
|
|
|
|
|
|
|
| 285 |
}
|
| 286 |
|
| 287 |
.card {
|
|
|
|
| 289 |
border-radius: 8px;
|
| 290 |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
| 291 |
overflow: hidden;
|
|
|
|
| 292 |
}
|
| 293 |
|
| 294 |
.card-header {
|
|
|
|
| 347 |
animation: typing 1.4s infinite ease-in-out;
|
| 348 |
}
|
| 349 |
|
| 350 |
+
.typing-dot:nth-child(1) {
|
| 351 |
+
animation-delay: 0s;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.typing-dot:nth-child(2) {
|
| 355 |
+
animation-delay: 0.2s;
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
.typing-dot:nth-child(3) {
|
| 359 |
+
animation-delay: 0.4s;
|
| 360 |
+
}
|
| 361 |
|
| 362 |
@keyframes typing {
|
| 363 |
0%, 60%, 100% { transform: translateY(0); }
|
|
|
|
| 365 |
}
|
| 366 |
|
| 367 |
/* Status indicators */
|
| 368 |
+
.connected {
|
| 369 |
+
color: var(--success);
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
.disconnected {
|
| 373 |
+
color: var(--danger);
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.expiring-soon {
|
| 377 |
+
color: var(--warning);
|
| 378 |
+
font-weight: bold;
|
| 379 |
+
}
|
| 380 |
|
| 381 |
/* Footer */
|
| 382 |
footer {
|
| 383 |
background: var(--dark);
|
| 384 |
color: white;
|
| 385 |
+
padding: 20px 0;
|
| 386 |
+
margin-top: auto;
|
| 387 |
}
|
| 388 |
|
| 389 |
.footer-content {
|
|
|
|
| 400 |
|
| 401 |
/* Responsive Design */
|
| 402 |
@media (max-width: 900px) {
|
|
|
|
|
|
|
|
|
|
| 403 |
.main-layout {
|
| 404 |
flex-direction: column;
|
|
|
|
| 405 |
}
|
| 406 |
+
|
| 407 |
+
.sidebar {
|
| 408 |
+
width: 100%;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
.message {
|
| 412 |
+
max-width: 90%;
|
| 413 |
+
}
|
| 414 |
}
|
| 415 |
|
| 416 |
@media (max-width: 600px) {
|
|
|
|
| 419 |
gap: 10px;
|
| 420 |
align-items: flex-start;
|
| 421 |
}
|
| 422 |
+
|
| 423 |
.footer-content {
|
| 424 |
flex-direction: column;
|
| 425 |
gap: 15px;
|
| 426 |
align-items: flex-start;
|
| 427 |
}
|
| 428 |
+
|
| 429 |
+
.input-area {
|
| 430 |
+
flex-direction: column;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
button {
|
| 434 |
+
padding: 12px;
|
| 435 |
+
}
|
| 436 |
}
|
| 437 |
</style>
|
| 438 |
</head>
|
|
|
|
| 453 |
</div>
|
| 454 |
</div>
|
| 455 |
</header>
|
| 456 |
+
|
| 457 |
+
<div class="container">
|
| 458 |
+
<div class="main-layout">
|
| 459 |
+
<!-- Chat Interface -->
|
| 460 |
+
<div class="chat-container">
|
| 461 |
+
<div class="chat-header">
|
| 462 |
+
<h2><i class="fas fa-comments"></i> Legal Consultation</h2>
|
| 463 |
+
<div class="status-indicator">
|
| 464 |
+
<div class="status-dot" id="statusDot"></div>
|
| 465 |
+
<span id="connectionStatus">Connecting...</span>
|
|
|
|
|
|
|
|
|
|
| 466 |
</div>
|
| 467 |
+
</div>
|
| 468 |
+
|
| 469 |
+
<div class="chat-history" id="chatHistory">
|
| 470 |
+
<div class="message ai-message">
|
| 471 |
+
<div class="message-header">
|
| 472 |
+
<span><i class="fas fa-robot"></i> Legal Assistant</span>
|
| 473 |
+
<span>Just now</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
</div>
|
| 475 |
+
<div class="message-content">
|
| 476 |
+
<p>Welcome to the Irish Legal AI Assistant! I'm here to help with your legal questions regarding Irish law.</p>
|
| 477 |
+
<p>Please ask your question in the box below. I'll provide a concise answer with relevant legal sources.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
</div>
|
| 479 |
</div>
|
| 480 |
</div>
|
| 481 |
|
| 482 |
+
<div class="input-container">
|
| 483 |
+
<div class="input-area">
|
| 484 |
+
<textarea id="userInput" placeholder="Ask a question about Irish law..." required></textarea>
|
| 485 |
+
<button id="sendButton">
|
| 486 |
+
<i class="fas fa-paper-plane"></i> Send
|
| 487 |
+
</button>
|
|
|
|
|
|
|
|
|
|
| 488 |
</div>
|
| 489 |
+
</div>
|
| 490 |
+
</div>
|
| 491 |
+
|
| 492 |
+
<!-- Sidebar -->
|
| 493 |
+
<div class="sidebar">
|
| 494 |
+
<div class="card">
|
| 495 |
+
<div class="card-header">
|
| 496 |
+
<i class="fas fa-history"></i> Session History
|
| 497 |
+
</div>
|
| 498 |
+
<div class="card-body" id="historyList">
|
| 499 |
+
<p style="color: var(--gray); text-align: center;">No history yet</p>
|
| 500 |
+
</div>
|
| 501 |
+
</div>
|
| 502 |
+
|
| 503 |
+
<div class="card">
|
| 504 |
+
<div class="card-header">
|
| 505 |
+
<i class="fas fa-lightbulb"></i> Tips for Better Results
|
| 506 |
+
</div>
|
| 507 |
+
<div class="card-body">
|
| 508 |
+
<ul style="padding-left: 20px; display: flex; flex-direction: column; gap: 10px;">
|
| 509 |
+
<li>Be specific with your questions</li>
|
| 510 |
+
<li>Include relevant context when possible</li>
|
| 511 |
+
<li>Ask about recent legal changes</li>
|
| 512 |
+
<li>Request practical implications</li>
|
| 513 |
+
<li>Specify areas of law (employment, property, etc.)</li>
|
| 514 |
+
</ul>
|
| 515 |
</div>
|
| 516 |
</div>
|
| 517 |
</div>
|
|
|
|
| 537 |
const connectionStatus = document.getElementById('connectionStatus');
|
| 538 |
const statusDot = document.getElementById('statusDot');
|
| 539 |
|
| 540 |
+
// Session management
|
| 541 |
let sessionId = null;
|
| 542 |
let sessionHistory = [];
|
| 543 |
let sessionCheckInterval;
|
| 544 |
+
const SESSION_CHECK_INTERVAL = 1000; // Check every second
|
| 545 |
|
| 546 |
+
// Initialize session
|
| 547 |
function initializeSession() {
|
| 548 |
updateConnectionStatus('connecting');
|
| 549 |
checkSessionStatus();
|
| 550 |
sessionCheckInterval = setInterval(checkSessionStatus, SESSION_CHECK_INTERVAL);
|
| 551 |
}
|
| 552 |
|
| 553 |
+
// Update connection status UI
|
| 554 |
function updateConnectionStatus(status) {
|
| 555 |
switch(status) {
|
| 556 |
case 'connected':
|
|
|
|
| 571 |
}
|
| 572 |
}
|
| 573 |
|
| 574 |
+
// Check session status with backend
|
| 575 |
async function checkSessionStatus() {
|
| 576 |
try {
|
| 577 |
const response = await fetch('session/status', {
|
| 578 |
method: 'GET',
|
| 579 |
credentials: 'include'
|
| 580 |
});
|
| 581 |
+
|
| 582 |
if (!response.ok) throw new Error('Status check failed');
|
| 583 |
+
|
| 584 |
const data = await response.json();
|
| 585 |
updateConnectionStatus('connected');
|
| 586 |
updateSessionDisplay(data);
|
| 587 |
+
|
| 588 |
+
if (data.status === 'expired') {
|
| 589 |
+
handleSessionExpiry();
|
| 590 |
+
}
|
| 591 |
} catch (error) {
|
| 592 |
console.error('Session status check error:', error);
|
| 593 |
updateConnectionStatus('disconnected');
|
|
|
|
| 595 |
}
|
| 596 |
}
|
| 597 |
|
| 598 |
+
// Update session timer display
|
| 599 |
function updateSessionDisplay(sessionData) {
|
| 600 |
switch (sessionData.status) {
|
| 601 |
case 'new':
|
|
|
|
| 605 |
sessionHistory = [];
|
| 606 |
updateSessionHistory([]);
|
| 607 |
break;
|
| 608 |
+
|
| 609 |
case 'expired':
|
| 610 |
sessionTimerElement.textContent = "Session Expired";
|
| 611 |
sessionTimerElement.classList.remove('expiring-soon');
|
|
|
|
| 613 |
sessionHistory = [];
|
| 614 |
updateSessionHistory([]);
|
| 615 |
break;
|
| 616 |
+
|
| 617 |
case 'active':
|
| 618 |
sessionId = sessionData.session_id;
|
| 619 |
const minutes = Math.floor(sessionData.ttl / 60);
|
| 620 |
const seconds = sessionData.ttl % 60;
|
| 621 |
+
|
| 622 |
+
// Add warning when session is about to expire
|
| 623 |
+
if (sessionData.ttl < 120) { // 2 minutes left
|
| 624 |
sessionTimerElement.classList.add('expiring-soon');
|
| 625 |
} else {
|
| 626 |
sessionTimerElement.classList.remove('expiring-soon');
|
| 627 |
}
|
| 628 |
+
|
| 629 |
sessionTimerElement.textContent = `Expires in ${minutes}:${seconds.toString().padStart(2, '0')}`;
|
| 630 |
+
|
| 631 |
+
// Update history if count changed
|
| 632 |
if (sessionData.history_count !== sessionHistory.length) {
|
| 633 |
fetchSessionHistory();
|
| 634 |
}
|
|
|
|
| 636 |
}
|
| 637 |
}
|
| 638 |
|
| 639 |
+
// Fetch full session history
|
| 640 |
async function fetchSessionHistory() {
|
| 641 |
try {
|
| 642 |
const response = await fetch('session/history', {
|
| 643 |
method: 'GET',
|
| 644 |
credentials: 'include'
|
| 645 |
});
|
| 646 |
+
|
| 647 |
if (!response.ok) throw new Error('History fetch failed');
|
| 648 |
+
|
| 649 |
const data = await response.json();
|
| 650 |
sessionHistory = data.history || [];
|
| 651 |
updateSessionHistory(sessionHistory);
|
|
|
|
| 655 |
}
|
| 656 |
}
|
| 657 |
|
| 658 |
+
// Update session history display
|
| 659 |
function updateSessionHistory(history) {
|
| 660 |
historyList.innerHTML = '';
|
| 661 |
+
|
| 662 |
if (history.length === 0) {
|
| 663 |
historyList.innerHTML = '<p style="color: var(--gray); text-align: center;">No history yet</p>';
|
| 664 |
return;
|
| 665 |
}
|
| 666 |
+
|
| 667 |
+
history.forEach((item, index) => {
|
| 668 |
const historyItem = document.createElement('div');
|
| 669 |
historyItem.className = 'history-item';
|
| 670 |
+
historyItem.innerHTML = `
|
| 671 |
+
<div class="history-question">${truncateText(item.q, 70)}</div>
|
| 672 |
+
`;
|
| 673 |
+
historyItem.addEventListener('click', () => {
|
| 674 |
+
scrollToMessage(item.q);
|
| 675 |
+
});
|
| 676 |
historyList.appendChild(historyItem);
|
| 677 |
});
|
| 678 |
}
|
| 679 |
|
| 680 |
+
// Scroll to message in chat
|
| 681 |
function scrollToMessage(query) {
|
| 682 |
const messages = document.querySelectorAll('.message');
|
| 683 |
for (let msg of messages) {
|
| 684 |
if (msg.textContent.includes(query)) {
|
| 685 |
msg.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
| 686 |
+
// Highlight briefly
|
| 687 |
msg.style.boxShadow = '0 0 0 2px var(--accent)';
|
| 688 |
+
setTimeout(() => {
|
| 689 |
+
msg.style.boxShadow = '';
|
| 690 |
+
}, 2000);
|
| 691 |
break;
|
| 692 |
}
|
| 693 |
}
|
| 694 |
}
|
| 695 |
|
| 696 |
+
// Truncate text for history display
|
| 697 |
function truncateText(text, maxLength) {
|
| 698 |
if (!text) return "";
|
| 699 |
return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
|
| 700 |
}
|
| 701 |
|
| 702 |
+
// Handle session expiry
|
| 703 |
function handleSessionExpiry() {
|
| 704 |
sessionId = null;
|
| 705 |
sessionHistory = [];
|
| 706 |
+
|
| 707 |
+
// Clear chat except initial message
|
| 708 |
while (chatHistory.children.length > 1) {
|
| 709 |
chatHistory.removeChild(chatHistory.lastChild);
|
| 710 |
}
|
| 711 |
+
|
| 712 |
+
// Add expiry notification
|
| 713 |
+
addMessage("Your session has expired due to inactivity. A new session will be created with your next question. Please be patient and retry if your request fails or you don’t get a response—your new session will begin once the timer resets.","ai");
|
| 714 |
+
|
| 715 |
+
// Update history display
|
| 716 |
updateSessionHistory([]);
|
| 717 |
}
|
| 718 |
|
| 719 |
+
// Handle sending messages
|
| 720 |
sendButton.addEventListener('click', sendMessage);
|
| 721 |
userInput.addEventListener('keypress', function(e) {
|
| 722 |
if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
|
| 729 |
const question = userInput.value.trim();
|
| 730 |
if (!question) return;
|
| 731 |
|
| 732 |
+
// Add user message to chat
|
| 733 |
addMessage(question, 'user');
|
| 734 |
userInput.value = '';
|
| 735 |
sendButton.disabled = true;
|
| 736 |
|
| 737 |
+
// Show typing indicator
|
| 738 |
const typingIndicator = document.createElement('div');
|
| 739 |
typingIndicator.className = 'typing-indicator';
|
| 740 |
typingIndicator.innerHTML = `
|
|
|
|
| 746 |
chatHistory.scrollTop = chatHistory.scrollHeight;
|
| 747 |
|
| 748 |
try {
|
| 749 |
+
// Send query to backend
|
| 750 |
const response = await fetch('query', {
|
| 751 |
method: 'POST',
|
| 752 |
+
headers: {
|
| 753 |
+
'Content-Type': 'application/json',
|
| 754 |
+
},
|
| 755 |
body: JSON.stringify({ query: question }),
|
| 756 |
credentials: 'include'
|
| 757 |
});
|
| 758 |
|
| 759 |
+
if (!response.ok) {
|
| 760 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 761 |
+
}
|
| 762 |
|
| 763 |
const data = await response.json();
|
| 764 |
+
|
| 765 |
+
// Remove typing indicator
|
| 766 |
chatHistory.removeChild(typingIndicator);
|
| 767 |
+
|
| 768 |
+
// Add AI response
|
| 769 |
addMessage(data.answer, 'ai', data.sources);
|
| 770 |
|
| 771 |
+
// Update session info
|
| 772 |
if (data.session_id) {
|
| 773 |
sessionId = data.session_id;
|
| 774 |
+
// Refresh history display
|
| 775 |
fetchSessionHistory();
|
| 776 |
}
|
| 777 |
+
|
| 778 |
} catch (error) {
|
| 779 |
console.error('Error sending message:', error);
|
| 780 |
chatHistory.removeChild(typingIndicator);
|
|
|
|
| 787 |
function addMessage(content, sender, sources = []) {
|
| 788 |
const messageDiv = document.createElement('div');
|
| 789 |
messageDiv.className = `message ${sender}-message`;
|
| 790 |
+
|
| 791 |
const now = new Date();
|
| 792 |
const timeString = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
| 793 |
+
|
| 794 |
messageDiv.innerHTML = `
|
| 795 |
<div class="message-header">
|
| 796 |
<span><i class="${sender === 'ai' ? 'fas fa-robot' : 'fas fa-user'}"></i> ${sender === 'ai' ? 'Legal Assistant' : 'You'}</span>
|
|
|
|
| 806 |
` : ''}
|
| 807 |
</div>
|
| 808 |
`;
|
| 809 |
+
|
| 810 |
chatHistory.appendChild(messageDiv);
|
| 811 |
chatHistory.scrollTop = chatHistory.scrollHeight;
|
| 812 |
}
|
| 813 |
|
| 814 |
function formatMessageContent(content) {
|
| 815 |
if (!content) return "";
|
| 816 |
+
// Convert line breaks to paragraphs
|
| 817 |
const paragraphs = content.split('\n\n');
|
| 818 |
return paragraphs.map(p => `<p>${p}</p>`).join('');
|
| 819 |
}
|
| 820 |
|
| 821 |
+
// Initialize the session
|
| 822 |
initializeSession();
|
| 823 |
});
|
| 824 |
</script>
|