| {% extends "base.html" %} |
|
|
| {% block content %} |
| <div class="min-h-screen bg-[#f8fafc] p-8 font-sans"> |
| <div class="max-w-7xl mx-auto"> |
|
|
|
|
| <div class="bg-white rounded-3xl shadow-sm border border-slate-200 p-2 mb-10"> |
| <form action="/rag" method="POST" enctype="multipart/form-data" onsubmit="return showLoading()" class="flex items-center gap-2"> |
| <label class="flex items-center justify-center px-6 py-4 border-r border-slate-100 cursor-pointer hover:bg-slate-50 transition-colors"> |
| <i class="fas fa-paperclip text-slate-400 mr-2"></i> |
| <span id="file-name" class="text-sm font-semibold text-slate-500 truncate max-w-[120px]">Upload PDF</span> |
| <input type="file" name="pdf_file" id="pdf_file" accept=".pdf" class="hidden" onchange="updateFileName(this)" required /> |
| </label> |
|
|
| <input type="text" name="q" id="query-input" placeholder="Ask anything about your document..." |
| class="flex-1 px-6 py-4 text-slate-700 outline-none placeholder:text-slate-400" required> |
|
|
| <button type="submit" id="submit-btn" class="bg-blue-600 text-white px-8 py-4 rounded-2xl font-bold hover:bg-blue-700 transition-all flex items-center gap-3 mr-2"> |
| <span id="btn-text">Analyze</span> |
| <div id="loading-spinner" class="hidden animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"></div> |
| </button> |
| </form> |
| </div> |
|
|
| {% if answer %} |
| <div id="results-area" class="grid grid-cols-1 lg:grid-cols-12 gap-8 animate-fade-in"> |
| <div class="lg:col-span-8"> |
| <div class="bg-white p-10 rounded-[2rem] border border-slate-200 shadow-sm min-h-[400px]"> |
| <div class="flex items-center justify-between mb-8"> |
| <h3 class="text-[11px] font-black text-indigo-500 uppercase tracking-[0.2em]">Generated Answer</h3> |
| <button onclick="copyAnswer()" class="flex items-center gap-2 text-xs font-bold text-slate-400 hover:text-slate-600 bg-slate-50 px-3 py-1.5 rounded-md transition-all"> |
| <i class="fas fa-copy"></i> Copy |
| </button> |
| </div> |
|
|
| <div id="answer-content" class="text-[#334155] text-lg leading-relaxed prose prose-slate max-w-none"> |
| {{ answer | safe }} |
| </div> |
| </div> |
| </div> |
|
|
| <div class="lg:col-span-4 space-y-6"> |
| <div class="flex items-center justify-between px-2"> |
| <h3 class="text-[11px] font-black text-slate-400 uppercase tracking-[0.2em]">Supporting Context</h3> |
| {% if sources %} |
| <span class="text-[10px] bg-indigo-50 text-indigo-600 px-2.5 py-1 rounded-full font-bold"> |
| {{ sources|length }} snippets |
| </span> |
| {% endif %} |
| </div> |
|
|
| <div class="space-y-4"> |
| {% if sources %} |
| {% for source in sources %} |
| <div class="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-all group relative"> |
| <div class="flex items-center justify-between mb-4"> |
| <span class="text-[10px] font-black text-slate-300 uppercase tracking-widest">Chunk {{ loop.index }}</span> |
| <button onclick="copySnippet({{ loop.index - 1 }})" class="text-slate-300 hover:text-indigo-500 transition-colors"> |
| <i class="fas fa-copy text-xs"></i> |
| </button> |
| </div> |
| <p class="text-[13px] text-slate-500 leading-relaxed snippet-text italic" data-full-text="{{ source }}"> |
| "{{ source[:220] }}{% if source|length > 220 %}...{% endif %}" |
| </p> |
| {% if source|length > 220 %} |
| <button onclick="toggleSnippet({{ loop.index - 1 }})" class="text-[10px] text-indigo-500 font-bold mt-3 uppercase tracking-wider hover:underline"> |
| Show More |
| </button> |
| {% endif %} |
| </div> |
| {% endfor %} |
| {% else %} |
| <div class="text-center py-10"> |
| <i class="fas fa-quote-right text-slate-200 text-3xl mb-3"></i> |
| <p class="text-sm text-slate-400 italic">No snippets found.</p> |
| </div> |
| {% endif %} |
| </div> |
| </div> |
| </div> |
| {% endif %} |
| </div> |
| </div> |
|
|
| <script> |
| function updateFileName(input) { |
| const fileName = document.getElementById('file-name'); |
| if (input.files && input.files[0]) { |
| fileName.textContent = input.files[0].name; |
| fileName.classList.remove('text-slate-500'); |
| fileName.classList.add('text-blue-600'); |
| } |
| } |
| |
| function showLoading() { |
| const btnText = document.getElementById('btn-text'); |
| const spinner = document.getElementById('loading-spinner'); |
| const btn = document.getElementById('submit-btn'); |
| |
| btn.disabled = true; |
| btn.classList.add('opacity-80'); |
| btnText.textContent = 'Analyzing...'; |
| spinner.classList.remove('hidden'); |
| return true; |
| } |
| |
| function copyAnswer() { |
| const text = document.getElementById('answer-content').innerText; |
| navigator.clipboard.writeText(text); |
| alert('Answer copied!'); |
| } |
| |
| function toggleSnippet(index) { |
| const snippets = document.querySelectorAll('.snippet-text'); |
| const buttons = document.querySelectorAll('.snippet-text + button'); |
| const snippet = snippets[index]; |
| const button = buttons[index]; |
| const fullText = snippet.getAttribute('data-full-text'); |
| |
| if (snippet.classList.contains('expanded')) { |
| snippet.textContent = `"${fullText.substring(0, 220)}..."`; |
| snippet.classList.remove('expanded'); |
| button.textContent = 'Show More'; |
| } else { |
| snippet.textContent = `"${fullText}"`; |
| snippet.classList.add('expanded'); |
| button.textContent = 'Show Less'; |
| } |
| } |
| </script> |
|
|
| <style> |
| @keyframes fade-in { |
| from { opacity: 0; transform: translateY(20px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .animate-fade-in { animation: fade-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; } |
| body { background-color: #f8fafc; } |
| .prose p { margin-bottom: 1.5rem; } |
| </style> |
| {% endblock %} |