Jun 30 - Add chat to AI analysis result
This commit is contained in:
+175
-3
@@ -96,6 +96,7 @@
|
||||
<button class="btn btn-secondary btn-sm" id="btn-copy" onclick="copyOutput()" style="display:none">📋 Copy</button>
|
||||
<button class="btn btn-secondary btn-sm" id="btn-save" onclick="saveOutput()" style="display:none">💾 Save as TXT</button>
|
||||
<button class="btn btn-secondary btn-sm" id="btn-clear" onclick="clearOutput()" style="display:none">🗑 Clear</button>
|
||||
<button class="btn btn-primary btn-sm" id="btn-chat-live" onclick="openChat(_liveAnalysisId)" style="display:none">💬 Chat</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -269,16 +270,33 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-danger btn-sm" id="btn-delete-analysis" style="margin-right:auto;display:none" onclick="deleteAnalysis()">🗑 Delete</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="openChat(_currentAnalysisId)">💬 Chat</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal('modal-history')">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Chat Modal ────────────────────────────────────────────── -->
|
||||
<div class="modal-overlay" id="modal-chat">
|
||||
<div class="modal chat-modal">
|
||||
<div class="modal-header">
|
||||
<span class="modal-title">💬 Ask AI about this document</span>
|
||||
<button class="modal-close" onclick="closeModal('modal-chat')">✕</button>
|
||||
</div>
|
||||
<div id="chat-messages" class="chat-messages"></div>
|
||||
<div class="chat-input-row">
|
||||
<input type="text" id="chat-input" class="chat-input" placeholder="Ask a question about this document…" autocomplete="off">
|
||||
<button class="btn btn-primary" id="btn-chat-send" onclick="sendChatMessage()">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- marked.js for markdown rendering -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
|
||||
|
||||
<script>
|
||||
var IS_ADMIN = {{ 'true' if is_admin else 'false' }};
|
||||
var _liveAnalysisId = null; // set after a live analysis completes
|
||||
|
||||
/* ─── Markdown renderer config ──────────────────────────────── */
|
||||
marked.setOptions({ breaks: true, gfm: true });
|
||||
@@ -340,6 +358,12 @@ function showResult(data) {
|
||||
document.getElementById('btn-copy').style.display = '';
|
||||
document.getElementById('btn-save').style.display = '';
|
||||
document.getElementById('btn-clear').style.display = '';
|
||||
|
||||
// Show chat button and remember which analysis is loaded
|
||||
if (data.analysis_id) {
|
||||
_liveAnalysisId = data.analysis_id;
|
||||
document.getElementById('btn-chat-live').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function clearOutput() {
|
||||
@@ -348,9 +372,11 @@ function clearOutput() {
|
||||
'<div class="ai-placeholder"><p>Upload a document and click <strong>Analyze with AI</strong> to begin.</p></div>';
|
||||
document.getElementById('verdict-banner').style.display = 'none';
|
||||
document.getElementById('verdict-banner').innerHTML = '';
|
||||
document.getElementById('btn-copy').style.display = 'none';
|
||||
document.getElementById('btn-save').style.display = 'none';
|
||||
document.getElementById('btn-clear').style.display = 'none';
|
||||
document.getElementById('btn-copy').style.display = 'none';
|
||||
document.getElementById('btn-save').style.display = 'none';
|
||||
document.getElementById('btn-clear').style.display = 'none';
|
||||
document.getElementById('btn-chat-live').style.display = 'none';
|
||||
_liveAnalysisId = null;
|
||||
}
|
||||
|
||||
function copyOutput() {
|
||||
@@ -638,6 +664,93 @@ function escHtml(str) {
|
||||
return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];
|
||||
});
|
||||
}
|
||||
|
||||
/* ─── Chat ──────────────────────────────────────────────────── */
|
||||
var _chatAnalysisId = null;
|
||||
var _chatMessages = [];
|
||||
|
||||
function openChat(analysisId) {
|
||||
if (!analysisId) { alert('No analysis loaded.'); return; }
|
||||
_chatAnalysisId = analysisId;
|
||||
_chatMessages = [];
|
||||
document.getElementById('chat-messages').innerHTML = '';
|
||||
document.getElementById('chat-input').value = '';
|
||||
// Close history modal if open, then open chat
|
||||
closeModal('modal-history');
|
||||
openModal('modal-chat');
|
||||
_appendChatMsg('assistant',
|
||||
'Hi! Ask me anything about this solicitation — dates, scope, contacts, requirements, and more.');
|
||||
setTimeout(function() { document.getElementById('chat-input').focus(); }, 150);
|
||||
}
|
||||
|
||||
function _appendChatMsg(role, content) {
|
||||
var wrap = document.createElement('div');
|
||||
wrap.className = 'chat-msg chat-msg-' + role;
|
||||
var bubble = document.createElement('div');
|
||||
bubble.className = 'chat-bubble';
|
||||
bubble.innerHTML = renderAI(content);
|
||||
wrap.appendChild(bubble);
|
||||
var box = document.getElementById('chat-messages');
|
||||
box.appendChild(wrap);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
|
||||
function _appendTyping() {
|
||||
var wrap = document.createElement('div');
|
||||
wrap.className = 'chat-msg chat-msg-assistant';
|
||||
wrap.id = 'chat-typing';
|
||||
wrap.innerHTML = '<div class="chat-bubble chat-typing"><span></span><span></span><span></span></div>';
|
||||
var box = document.getElementById('chat-messages');
|
||||
box.appendChild(wrap);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
|
||||
async function sendChatMessage() {
|
||||
var input = document.getElementById('chat-input');
|
||||
var text = input.value.trim();
|
||||
if (!text || !_chatAnalysisId) return;
|
||||
|
||||
var sendBtn = document.getElementById('btn-chat-send');
|
||||
input.value = '';
|
||||
input.disabled = true;
|
||||
sendBtn.disabled = true;
|
||||
|
||||
_chatMessages.push({ role: 'user', content: text });
|
||||
_appendChatMsg('user', text);
|
||||
_appendTyping();
|
||||
|
||||
try {
|
||||
var resp = await fetch('/ai-summary/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken() },
|
||||
body: JSON.stringify({ analysis_id: _chatAnalysisId, messages: _chatMessages }),
|
||||
});
|
||||
var data = await resp.json();
|
||||
var typing = document.getElementById('chat-typing');
|
||||
if (typing) typing.remove();
|
||||
|
||||
if (data.error) {
|
||||
_chatMessages.pop(); // discard the failed user message
|
||||
_appendChatMsg('assistant', '⚠ ' + escHtml(data.error));
|
||||
} else {
|
||||
_chatMessages.push({ role: 'assistant', content: data.reply });
|
||||
_appendChatMsg('assistant', data.reply);
|
||||
}
|
||||
} catch(e) {
|
||||
var typing = document.getElementById('chat-typing');
|
||||
if (typing) typing.remove();
|
||||
_chatMessages.pop();
|
||||
_appendChatMsg('assistant', '⚠ Request failed. Please try again.');
|
||||
} finally {
|
||||
input.disabled = false;
|
||||
sendBtn.disabled = false;
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('chat-input').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChatMessage(); }
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -749,5 +862,64 @@ function escHtml(str) {
|
||||
.ai-layout{grid-template-columns:1fr}
|
||||
.ai-output-body{max-height:60vh}
|
||||
}
|
||||
|
||||
/* ── Chat modal ────────────────────────────────────────────── */
|
||||
.chat-modal{
|
||||
max-width:660px;width:100%;
|
||||
display:flex;flex-direction:column;
|
||||
height:75vh;max-height:600px;
|
||||
}
|
||||
.chat-messages{
|
||||
flex:1;overflow-y:auto;
|
||||
padding:.85rem 1.2rem;
|
||||
display:flex;flex-direction:column;gap:.6rem;
|
||||
background:var(--bg-subtle);
|
||||
}
|
||||
.chat-msg{display:flex}
|
||||
.chat-msg-user{justify-content:flex-end}
|
||||
.chat-msg-assistant{justify-content:flex-start}
|
||||
.chat-bubble{
|
||||
max-width:82%;padding:.55rem .85rem;
|
||||
border-radius:1rem;font-size:.865rem;line-height:1.6;
|
||||
}
|
||||
.chat-msg-user .chat-bubble{
|
||||
background:var(--accent);color:#fff;
|
||||
border-bottom-right-radius:.25rem;
|
||||
}
|
||||
.chat-msg-assistant .chat-bubble{
|
||||
background:var(--bg-card,#fff);color:var(--text);
|
||||
border:1px solid var(--border);
|
||||
border-bottom-left-radius:.25rem;
|
||||
}
|
||||
.chat-bubble p{margin:.2rem 0}
|
||||
.chat-bubble p:first-child{margin-top:0}
|
||||
.chat-bubble p:last-child{margin-bottom:0}
|
||||
.chat-bubble ul,.chat-bubble ol{margin:.3rem 0;padding-left:1.2rem}
|
||||
.chat-bubble li{margin:.15rem 0}
|
||||
.chat-bubble strong{font-weight:700}
|
||||
.chat-bubble table{border-collapse:collapse;font-size:.82rem;width:100%}
|
||||
.chat-bubble th,.chat-bubble td{border:1px solid var(--border);padding:.2rem .5rem;text-align:left}
|
||||
.chat-msg-user .chat-bubble strong{color:#fff}
|
||||
/* Typing indicator */
|
||||
.chat-typing{display:flex;align-items:center;gap:4px;padding:.55rem .75rem}
|
||||
.chat-typing span{
|
||||
display:inline-block;width:7px;height:7px;border-radius:50%;
|
||||
background:var(--text-muted);animation:chatBounce 1.2s infinite;
|
||||
}
|
||||
.chat-typing span:nth-child(2){animation-delay:.2s}
|
||||
.chat-typing span:nth-child(3){animation-delay:.4s}
|
||||
@keyframes chatBounce{0%,80%,100%{transform:translateY(0)}40%{transform:translateY(-6px)}}
|
||||
/* Input row */
|
||||
.chat-input-row{
|
||||
display:flex;gap:.5rem;padding:.75rem 1rem;
|
||||
border-top:1px solid var(--border);background:var(--bg-card,#fff);
|
||||
border-radius:0 0 var(--r) var(--r);
|
||||
}
|
||||
.chat-input{
|
||||
flex:1;padding:.45rem .75rem;border:1px solid var(--border);
|
||||
border-radius:var(--r-sm);font-family:inherit;font-size:.875rem;
|
||||
background:var(--bg);color:var(--text);
|
||||
}
|
||||
.chat-input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-light,rgba(59,130,246,.15))}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user