|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
|
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>WHEC Chatbot • AI Research Assistant</title> |
|
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
|
|
<link |
|
|
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" |
|
|
rel="stylesheet"> |
|
|
<style> |
|
|
:root { |
|
|
--primary: #3b82f6; |
|
|
--primary-glow: rgba(59, 130, 246, 0.5); |
|
|
--bg-dark: #0f172a; |
|
|
--bg-card: #1e293b; |
|
|
--text-main: #f8fafc; |
|
|
--text-muted: #94a3b8; |
|
|
--border-color: #334155; |
|
|
--accent-gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); |
|
|
} |
|
|
|
|
|
* { |
|
|
box-sizing: border-box; |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
} |
|
|
|
|
|
html, body { |
|
|
height: 100%; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: 'Outfit', sans-serif; |
|
|
background-color: var(--bg-dark); |
|
|
color: var(--text-main); |
|
|
line-height: 1.6; |
|
|
background-image: |
|
|
radial-gradient(circle at 10% 20%, rgba(59, 130, 246, 0.1) 0%, transparent 20%), |
|
|
radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.1) 0%, transparent 20%); |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
} |
|
|
|
|
|
|
|
|
header { |
|
|
text-align: center; |
|
|
padding: 2rem 2rem 1rem; |
|
|
border-bottom: 1px solid var(--border-color); |
|
|
background: rgba(15, 23, 42, 0.8); |
|
|
backdrop-filter: blur(10px); |
|
|
flex-shrink: 0; |
|
|
} |
|
|
|
|
|
h1 { |
|
|
font-family: 'Space Grotesk', sans-serif; |
|
|
font-size: 2rem; |
|
|
font-weight: 700; |
|
|
background: var(--accent-gradient); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
margin-bottom: 0.25rem; |
|
|
letter-spacing: -0.02em; |
|
|
} |
|
|
|
|
|
.subtitle { |
|
|
color: var(--text-muted); |
|
|
font-size: 0.9rem; |
|
|
font-weight: 300; |
|
|
} |
|
|
|
|
|
.subtitle a { |
|
|
color: var(--primary); |
|
|
text-decoration: none; |
|
|
} |
|
|
|
|
|
.subtitle a:hover { |
|
|
text-decoration: underline; |
|
|
} |
|
|
|
|
|
|
|
|
.action-buttons { |
|
|
display: flex; |
|
|
gap: 0.75rem; |
|
|
justify-content: center; |
|
|
margin-top: 1rem; |
|
|
} |
|
|
|
|
|
.action-buttons button { |
|
|
background: rgba(139, 92, 246, 0.2); |
|
|
color: #c4b5fd; |
|
|
border: 1px solid #8b5cf6; |
|
|
padding: 0.5rem 1rem; |
|
|
border-radius: 0.5rem; |
|
|
font-weight: 600; |
|
|
font-size: 0.85rem; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 0.5rem; |
|
|
} |
|
|
|
|
|
.action-buttons button:hover:not(:disabled) { |
|
|
background: rgba(139, 92, 246, 0.3); |
|
|
border-color: #a78bfa; |
|
|
} |
|
|
|
|
|
.action-buttons button:disabled { |
|
|
opacity: 0.5; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
|
|
|
#clearBtn { |
|
|
background: rgba(239, 68, 68, 0.2); |
|
|
color: #fca5a5; |
|
|
border-color: #ef4444; |
|
|
} |
|
|
|
|
|
#clearBtn:hover { |
|
|
background: rgba(239, 68, 68, 0.3); |
|
|
border-color: #f87171; |
|
|
} |
|
|
|
|
|
|
|
|
.chat-container { |
|
|
flex: 1; |
|
|
overflow-y: auto; |
|
|
padding: 2rem; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
} |
|
|
|
|
|
|
|
|
.empty-state { |
|
|
margin: auto; |
|
|
text-align: center; |
|
|
max-width: 600px; |
|
|
padding: 2rem; |
|
|
} |
|
|
|
|
|
.empty-state h2 { |
|
|
font-size: 1.5rem; |
|
|
color: var(--text-main); |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
|
|
|
.empty-state p { |
|
|
color: var(--text-muted); |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
|
|
|
.suggestions { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
|
|
gap: 1rem; |
|
|
margin-top: 1rem; |
|
|
} |
|
|
|
|
|
.suggestion-card { |
|
|
background: rgba(30, 41, 59, 0.5); |
|
|
border: 1px solid var(--border-color); |
|
|
border-radius: 0.75rem; |
|
|
padding: 1rem; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s; |
|
|
text-align: left; |
|
|
} |
|
|
|
|
|
.suggestion-card:hover { |
|
|
background: rgba(30, 41, 59, 0.8); |
|
|
border-color: var(--primary); |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
|
|
|
.suggestion-card .icon { |
|
|
font-size: 1.5rem; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
.suggestion-card .text { |
|
|
color: var(--text-main); |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
|
|
|
#chatHistory { |
|
|
max-width: 900px; |
|
|
width: 100%; |
|
|
margin: 0 auto; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
gap: 1.5rem; |
|
|
} |
|
|
|
|
|
.message-block { |
|
|
border: 1px solid var(--border-color); |
|
|
border-radius: 1rem; |
|
|
padding: 1.5rem; |
|
|
background: var(--bg-card); |
|
|
animation: fadeInUp 0.4s ease-out; |
|
|
} |
|
|
|
|
|
.user-question { |
|
|
margin-bottom: 1rem; |
|
|
color: var(--text-main); |
|
|
font-size: 0.95rem; |
|
|
} |
|
|
|
|
|
.user-question strong { |
|
|
color: var(--primary); |
|
|
} |
|
|
|
|
|
.assistant-answer { |
|
|
margin-bottom: 1rem; |
|
|
line-height: 1.8; |
|
|
font-size: 0.95rem; |
|
|
} |
|
|
|
|
|
.assistant-answer strong { |
|
|
color: #8b5cf6; |
|
|
} |
|
|
|
|
|
|
|
|
.sources-section { |
|
|
margin-top: 1rem; |
|
|
} |
|
|
|
|
|
.sources-header { |
|
|
font-weight: 600; |
|
|
color: var(--primary); |
|
|
margin-bottom: 0.75rem; |
|
|
font-size: 0.9rem; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 0.5rem; |
|
|
} |
|
|
|
|
|
.source-card { |
|
|
background: rgba(30, 41, 59, 0.4); |
|
|
border: 1px solid var(--border-color); |
|
|
border-radius: 0.75rem; |
|
|
padding: 1rem; |
|
|
margin-bottom: 0.75rem; |
|
|
transition: all 0.2s; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.source-card:hover { |
|
|
background: rgba(30, 41, 59, 0.8); |
|
|
border-color: var(--primary); |
|
|
transform: translateX(5px); |
|
|
} |
|
|
|
|
|
.source-title { |
|
|
font-weight: 600; |
|
|
color: var(--primary); |
|
|
margin-bottom: 0.5rem; |
|
|
font-size: 0.85rem; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 0.5rem; |
|
|
} |
|
|
|
|
|
.source-excerpt { |
|
|
font-style: italic; |
|
|
color: #cbd5e1; |
|
|
font-size: 0.85rem; |
|
|
background: rgba(0, 0, 0, 0.2); |
|
|
padding: 0.75rem; |
|
|
border-radius: 0.5rem; |
|
|
border-left: 3px solid var(--primary); |
|
|
word-wrap: break-word; |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
.source-meta { |
|
|
margin-top: 0.75rem; |
|
|
font-size: 0.75rem; |
|
|
color: var(--text-muted); |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.view-source-link { |
|
|
color: var(--primary); |
|
|
text-decoration: none; |
|
|
font-weight: 500; |
|
|
display: inline-flex; |
|
|
align-items: center; |
|
|
gap: 0.25rem; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.view-source-link:hover { |
|
|
color: #60a5fa; |
|
|
text-decoration: underline; |
|
|
} |
|
|
|
|
|
|
|
|
.images-section { |
|
|
margin-top: 1.5rem; |
|
|
} |
|
|
|
|
|
.images-grid { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); |
|
|
gap: 1rem; |
|
|
margin-top: 1rem; |
|
|
} |
|
|
|
|
|
.image-card { |
|
|
background: rgba(30, 41, 59, 0.6); |
|
|
border-radius: 0.75rem; |
|
|
overflow: hidden; |
|
|
border: 1px solid var(--border-color); |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.image-card:hover { |
|
|
transform: translateY(-5px); |
|
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); |
|
|
border-color: var(--primary); |
|
|
} |
|
|
|
|
|
.image-card a { |
|
|
display: block; |
|
|
overflow: hidden; |
|
|
height: 180px; |
|
|
} |
|
|
|
|
|
.image-card img { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
object-fit: cover; |
|
|
transition: transform 0.5s ease; |
|
|
} |
|
|
|
|
|
.image-card:hover img { |
|
|
transform: scale(1.1); |
|
|
} |
|
|
|
|
|
.image-meta { |
|
|
padding: 0.75rem; |
|
|
border-top: 1px solid var(--border-color); |
|
|
} |
|
|
|
|
|
.image-filename { |
|
|
font-weight: 600; |
|
|
color: #f1f5f9; |
|
|
font-size: 0.8rem; |
|
|
white-space: nowrap; |
|
|
overflow: hidden; |
|
|
text-overflow: ellipsis; |
|
|
margin-bottom: 0.25rem; |
|
|
} |
|
|
|
|
|
.image-source { |
|
|
font-size: 0.7rem; |
|
|
color: var(--text-muted); |
|
|
} |
|
|
|
|
|
|
|
|
.loading-message { |
|
|
max-width: 900px; |
|
|
width: 100%; |
|
|
margin: 0 auto; |
|
|
padding: 1.5rem; |
|
|
background: var(--bg-card); |
|
|
border: 1px solid var(--border-color); |
|
|
border-radius: 1rem; |
|
|
display: none; |
|
|
align-items: center; |
|
|
gap: 1rem; |
|
|
} |
|
|
|
|
|
.loading-message.active { |
|
|
display: flex; |
|
|
} |
|
|
|
|
|
.spinner { |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
border: 2px solid rgba(59, 130, 246, 0.3); |
|
|
border-radius: 50%; |
|
|
border-top-color: var(--primary); |
|
|
animation: spin 1s linear infinite; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
to { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
|
|
|
.input-container { |
|
|
border-top: 1px solid var(--border-color); |
|
|
background: rgba(15, 23, 42, 0.95); |
|
|
backdrop-filter: blur(10px); |
|
|
padding: 1.5rem 2rem; |
|
|
flex-shrink: 0; |
|
|
} |
|
|
|
|
|
.input-wrapper { |
|
|
max-width: 900px; |
|
|
margin: 0 auto; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.input-group { |
|
|
position: relative; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 0.75rem; |
|
|
background: rgba(30, 41, 59, 0.7); |
|
|
padding: 0.5rem; |
|
|
border-radius: 1.5rem; |
|
|
border: 1px solid var(--border-color); |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
|
|
|
.input-group:focus-within { |
|
|
border-color: var(--primary); |
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); |
|
|
} |
|
|
|
|
|
.input-group textarea { |
|
|
flex: 1; |
|
|
background: transparent; |
|
|
border: none; |
|
|
padding: 0.75rem 1rem; |
|
|
font-size: 1rem; |
|
|
color: white; |
|
|
font-family: 'Outfit', sans-serif; |
|
|
resize: none; |
|
|
max-height: 200px; |
|
|
min-height: 24px; |
|
|
overflow-y: auto; |
|
|
} |
|
|
|
|
|
.input-group textarea:focus { |
|
|
outline: none; |
|
|
} |
|
|
|
|
|
.input-group textarea::placeholder { |
|
|
color: #64748b; |
|
|
} |
|
|
|
|
|
.send-button { |
|
|
background: var(--accent-gradient); |
|
|
color: white; |
|
|
border: none; |
|
|
padding: 0.75rem; |
|
|
border-radius: 1rem; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
flex-shrink: 0; |
|
|
} |
|
|
|
|
|
.send-button:hover:not(:disabled) { |
|
|
opacity: 0.9; |
|
|
transform: scale(1.05); |
|
|
} |
|
|
|
|
|
.send-button:disabled { |
|
|
opacity: 0.5; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
|
|
|
|
|
|
::-webkit-scrollbar { |
|
|
width: 8px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-track { |
|
|
background: var(--bg-dark); |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-thumb { |
|
|
background: var(--border-color); |
|
|
border-radius: 4px; |
|
|
} |
|
|
|
|
|
::-webkit-scrollbar-thumb:hover { |
|
|
background: #475569; |
|
|
} |
|
|
|
|
|
|
|
|
@keyframes fadeInUp { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: translateY(20px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: translateY(0); |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
|
|
|
<body> |
|
|
|
|
|
<header> |
|
|
<h1>WHEC Chatbot</h1> |
|
|
<p class="subtitle"> |
|
|
AI Research Assistant for WHEC (Warrior Heat- and Exertion-Related Events Collaborative) |
|
|
<a href="https://www.hprc-online.org/resources-partners/whec" target="_blank">Learn more</a> |
|
|
</p> |
|
|
|
|
|
<div class="action-buttons" id="actionButtons" style="display: none;"> |
|
|
<button id="downloadBtn" onclick="downloadPDFReport()"> |
|
|
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
|
d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> |
|
|
</svg> |
|
|
Download Report |
|
|
</button> |
|
|
<button id="clearBtn" onclick="clearHistory()"> |
|
|
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path> |
|
|
</svg> |
|
|
Clear Chat |
|
|
</button> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
|
|
|
<div class="chat-container" id="chatContainer"> |
|
|
|
|
|
<div class="empty-state" id="emptyState"> |
|
|
<h2>Welcome to WHEC Research Assistant</h2> |
|
|
<p>Ask questions about heat and exertion-related events, medical research, and military health topics.</p> |
|
|
|
|
|
<div class="suggestions"> |
|
|
<div class="suggestion-card" onclick="askSuggestion('What is WHEC?')"> |
|
|
<div class="icon">🏥</div> |
|
|
<div class="text">What is WHEC?</div> |
|
|
</div> |
|
|
<div class="suggestion-card" onclick="askSuggestion('What are the symptoms of heat stroke?')"> |
|
|
<div class="icon">🌡️</div> |
|
|
<div class="text">Symptoms of heat stroke</div> |
|
|
</div> |
|
|
<div class="suggestion-card" onclick="askSuggestion('How to prevent exertional heat illness?')"> |
|
|
<div class="icon">🛡️</div> |
|
|
<div class="text">Prevention strategies</div> |
|
|
</div> |
|
|
<div class="suggestion-card" onclick="askSuggestion('What is the Heat Toolkit?')"> |
|
|
<div class="icon">📚</div> |
|
|
<div class="text">About Heat Toolkit</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="chatHistory"></div> |
|
|
|
|
|
|
|
|
<div class="loading-message" id="loadingMessage"> |
|
|
<div class="spinner"></div> |
|
|
<span>Searching documents and generating response...</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="input-container"> |
|
|
<div class="input-wrapper"> |
|
|
<div class="input-group"> |
|
|
<textarea |
|
|
id="messageInput" |
|
|
placeholder="Ask about exertion-related injuries, heat illness, or military health topics..." |
|
|
rows="1" |
|
|
></textarea> |
|
|
<button class="send-button" id="sendButton" onclick="sendMessage()"> |
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
|
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path> |
|
|
</svg> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const messageInput = document.getElementById('messageInput'); |
|
|
const sendButton = document.getElementById('sendButton'); |
|
|
const chatHistory = document.getElementById('chatHistory'); |
|
|
const chatContainer = document.getElementById('chatContainer'); |
|
|
const emptyState = document.getElementById('emptyState'); |
|
|
const loadingMessage = document.getElementById('loadingMessage'); |
|
|
const actionButtons = document.getElementById('actionButtons'); |
|
|
const downloadBtn = document.getElementById('downloadBtn'); |
|
|
|
|
|
let conversationData = []; |
|
|
|
|
|
|
|
|
messageInput.addEventListener('input', function() { |
|
|
this.style.height = 'auto'; |
|
|
this.style.height = Math.min(this.scrollHeight, 200) + 'px'; |
|
|
}); |
|
|
|
|
|
|
|
|
messageInput.addEventListener('keydown', function(e) { |
|
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
|
e.preventDefault(); |
|
|
sendMessage(); |
|
|
} |
|
|
}); |
|
|
|
|
|
function escapeHtml(text) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = text; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
function askSuggestion(question) { |
|
|
messageInput.value = question; |
|
|
sendMessage(); |
|
|
} |
|
|
|
|
|
async function sendMessage() { |
|
|
const question = messageInput.value.trim(); |
|
|
if (!question) return; |
|
|
|
|
|
|
|
|
if (emptyState) { |
|
|
emptyState.style.display = 'none'; |
|
|
} |
|
|
|
|
|
|
|
|
messageInput.value = ''; |
|
|
messageInput.style.height = 'auto'; |
|
|
|
|
|
|
|
|
messageInput.disabled = true; |
|
|
sendButton.disabled = true; |
|
|
|
|
|
|
|
|
loadingMessage.classList.add('active'); |
|
|
|
|
|
|
|
|
chatContainer.scrollTop = chatContainer.scrollHeight; |
|
|
|
|
|
try { |
|
|
const response = await fetch('/query', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ question: question }) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`Server error: ${response.status}`); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
|
|
|
conversationData.push({ |
|
|
question: question, |
|
|
answer: data.answer, |
|
|
images: data.images || [], |
|
|
texts: data.texts || [], |
|
|
timestamp: new Date().toISOString() |
|
|
}); |
|
|
|
|
|
|
|
|
actionButtons.style.display = 'flex'; |
|
|
|
|
|
|
|
|
const messageBlock = document.createElement('div'); |
|
|
messageBlock.className = 'message-block'; |
|
|
|
|
|
|
|
|
const userQuestion = document.createElement('div'); |
|
|
userQuestion.className = 'user-question'; |
|
|
userQuestion.innerHTML = `<strong>You:</strong> ${escapeHtml(question)}`; |
|
|
messageBlock.appendChild(userQuestion); |
|
|
|
|
|
|
|
|
const assistantAnswer = document.createElement('div'); |
|
|
assistantAnswer.className = 'assistant-answer'; |
|
|
assistantAnswer.innerHTML = `<strong>Assistant:</strong><br>${escapeHtml(data.answer).replace(/\n/g, '<br>')}`; |
|
|
messageBlock.appendChild(assistantAnswer); |
|
|
|
|
|
|
|
|
if (data.texts && data.texts.length > 0) { |
|
|
const sourcesSection = document.createElement('div'); |
|
|
sourcesSection.className = 'sources-section'; |
|
|
|
|
|
const sourcesHeader = document.createElement('div'); |
|
|
sourcesHeader.className = 'sources-header'; |
|
|
sourcesHeader.innerHTML = ` |
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> |
|
|
</svg> |
|
|
Referenced Sources |
|
|
`; |
|
|
sourcesSection.appendChild(sourcesHeader); |
|
|
|
|
|
const topTexts = data.texts.slice(0, 3); |
|
|
|
|
|
topTexts.forEach((txt) => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'source-card'; |
|
|
const truncatedText = txt.text.length > 250 ? txt.text.substring(0, 250) + '...' : txt.text; |
|
|
|
|
|
const sourceLink = txt.link ? `<a href="${escapeHtml(txt.link)}" target="_blank" class="view-source-link" onclick="event.stopPropagation()"> |
|
|
View in document → |
|
|
</a>` : ''; |
|
|
|
|
|
div.innerHTML = ` |
|
|
<div class="source-title"> |
|
|
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
|
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path> |
|
|
</svg> |
|
|
${escapeHtml(txt.file || 'Document')} |
|
|
</div> |
|
|
<div class="source-excerpt">"${escapeHtml(truncatedText)}"</div> |
|
|
<div class="source-meta"> |
|
|
<span>Page ${escapeHtml(String(txt.page || 'N/A'))} • ${Math.round((txt.score || 0) * 100)}% match</span> |
|
|
${sourceLink} |
|
|
</div> |
|
|
`; |
|
|
|
|
|
if (txt.link) { |
|
|
div.style.cursor = 'pointer'; |
|
|
div.onclick = () => window.open(txt.link, '_blank'); |
|
|
} |
|
|
|
|
|
sourcesSection.appendChild(div); |
|
|
}); |
|
|
|
|
|
messageBlock.appendChild(sourcesSection); |
|
|
} |
|
|
|
|
|
|
|
|
if (data.images && data.images.length > 0) { |
|
|
const relevantImages = data.images.filter(img => (img.score || 0) >= 0.3).slice(0, 2); |
|
|
|
|
|
if (relevantImages.length > 0) { |
|
|
const imagesSection = document.createElement('div'); |
|
|
imagesSection.className = 'images-section'; |
|
|
|
|
|
const imagesHeader = document.createElement('div'); |
|
|
imagesHeader.className = 'sources-header'; |
|
|
imagesHeader.innerHTML = ` |
|
|
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
|
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path> |
|
|
</svg> |
|
|
Referenced Figures |
|
|
`; |
|
|
imagesSection.appendChild(imagesHeader); |
|
|
|
|
|
const imagesWrapper = document.createElement('div'); |
|
|
imagesWrapper.className = 'images-grid'; |
|
|
|
|
|
relevantImages.forEach(img => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'image-card'; |
|
|
div.innerHTML = ` |
|
|
<a href="${escapeHtml(img.path || '')}" target="_blank"> |
|
|
<img src="${escapeHtml(img.path || '')}" alt="${escapeHtml(img.filename || 'Image')}" onerror="this.style.display='none'"> |
|
|
</a> |
|
|
<div class="image-meta"> |
|
|
<div class="image-filename">${escapeHtml(img.filename || 'Unknown')}</div> |
|
|
<div class="image-source"> |
|
|
${escapeHtml(img.file || 'Unknown')} • Page ${escapeHtml(String(img.page || 'N/A'))} |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
imagesWrapper.appendChild(div); |
|
|
}); |
|
|
|
|
|
imagesSection.appendChild(imagesWrapper); |
|
|
messageBlock.appendChild(imagesSection); |
|
|
} |
|
|
} |
|
|
|
|
|
chatHistory.appendChild(messageBlock); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
chatContainer.scrollTop = chatContainer.scrollHeight; |
|
|
}, 100); |
|
|
|
|
|
} catch (error) { |
|
|
alert('Error: ' + error.message); |
|
|
console.error('Error:', error); |
|
|
} finally { |
|
|
|
|
|
loadingMessage.classList.remove('active'); |
|
|
|
|
|
|
|
|
messageInput.disabled = false; |
|
|
sendButton.disabled = false; |
|
|
messageInput.focus(); |
|
|
} |
|
|
} |
|
|
|
|
|
async function downloadPDFReport() { |
|
|
if (conversationData.length === 0) { |
|
|
alert('No conversation to download'); |
|
|
return; |
|
|
} |
|
|
|
|
|
downloadBtn.disabled = true; |
|
|
const originalText = downloadBtn.innerHTML; |
|
|
downloadBtn.innerHTML = '<span style="font-size: 0.85em">Generating...</span>'; |
|
|
|
|
|
try { |
|
|
const response = await fetch('/generate-report', { |
|
|
method: 'POST', |
|
|
headers: { 'Content-Type': 'application/json' }, |
|
|
body: JSON.stringify({ conversations: conversationData }) |
|
|
}); |
|
|
|
|
|
if (!response.ok) throw new Error(`Server error: ${response.status}`); |
|
|
|
|
|
const blob = await response.blob(); |
|
|
const url = URL.createObjectURL(blob); |
|
|
const a = document.createElement('a'); |
|
|
a.href = url; |
|
|
a.download = `WHEC_Report_${new Date().toISOString().split('T')[0]}.pdf`; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
URL.revokeObjectURL(url); |
|
|
|
|
|
} catch (error) { |
|
|
alert('Error generating report: ' + error.message); |
|
|
} finally { |
|
|
downloadBtn.disabled = false; |
|
|
downloadBtn.innerHTML = originalText; |
|
|
} |
|
|
} |
|
|
|
|
|
function clearHistory() { |
|
|
if (conversationData.length === 0) return; |
|
|
|
|
|
if (!confirm('Clear all conversation history? This cannot be undone.')) return; |
|
|
|
|
|
conversationData = []; |
|
|
chatHistory.innerHTML = ''; |
|
|
actionButtons.style.display = 'none'; |
|
|
emptyState.style.display = 'block'; |
|
|
} |
|
|
|
|
|
|
|
|
messageInput.focus(); |
|
|
</script> |
|
|
</body> |
|
|
|
|
|
</html> |