Add chat endpoint, model selector, and improved AI analysis

This commit is contained in:
JC Beasley
2026-07-02 15:52:48 -07:00
parent e0654429dc
commit b552f27f52
2 changed files with 201 additions and 1 deletions
+166 -1
View File
@@ -334,6 +334,13 @@
<textarea id="surveyDescription" placeholder="Brief description of the survey purpose..."></textarea>
</div>
<div class="form-group">
<label>🤖 AI Model for Analysis</label>
<select id="createModelSelect">
<option value="">Loading models...</option>
</select>
</div>
<div id="surveyQuestions"></div>
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border);">
@@ -392,6 +399,18 @@
<button class="btn" onclick="exportResults('email')">📧 Email Results</button>
</div>
</div>
<!-- Chat Interface -->
<div id="chatInterface" style="margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border); display: none;">
<h3>💬 Ask AI About This Survey</h3>
<div id="chatMessages" style="background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 15px; margin-bottom: 15px; max-height: 300px; overflow-y: auto;">
<div style="color: var(--text-2); text-align: center; padding: 20px;">Click "Generate Recommendations" first to enable chat</div>
</div>
<div style="display: flex; gap: 10px;">
<input type="text" id="chatInput" placeholder="Ask a question about the network infrastructure..." style="flex: 1;">
<button class="btn btn-primary" onclick="sendChatMessage()">Send</button>
</div>
</div>
</div>
</div>
</div>
@@ -881,6 +900,135 @@
}
}
// Chat functionality
let chatHistory = [];
async function sendChatMessage() {
const input = document.getElementById('chatInput');
const messagesDiv = document.getElementById('chatMessages');
const message = input.value.trim();
if (!message) return;
if (!currentSurvey) {
alert('Please select a survey first');
return;
}
// Add user message
chatHistory.push({role: 'user', content: message});
appendChatMessage('You', message, 'user');
input.value = '';
try {
const modelSelect = document.getElementById('analyzeModelSelect');
const selectedModel = modelSelect.value || defaultModel;
const response = await fetch(`/api/surveys/${currentSurveyId}/chat`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message: message,
history: chatHistory,
model: selectedModel
})
});
const data = await response.json();
if (data.response) {
chatHistory.push({role: 'assistant', content: data.response});
appendChatMessage('AI', data.response, 'assistant');
} else {
appendChatMessage('Error', data.error || 'Failed to get response', 'error');
}
} catch (e) {
console.error('Chat error:', e);
appendChatMessage('Error', 'Failed to send message', 'error');
}
}
function appendChatMessage(sender, text, type) {
const messagesDiv = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `
margin-bottom: 10px;
padding: 10px;
border-radius: var(--radius);
background: ${type === 'user' ? 'rgba(88, 166, 255, 0.1)' : type === 'error' ? 'rgba(248, 81, 73, 0.1)' : 'var(--bg-3)'};
border-left: 3px solid ${type === 'user' ? 'var(--accent)' : type === 'error' ? 'var(--danger)' : 'var(--success)'};
`;
messageDiv.innerHTML = `
<div style="font-size: 0.8rem; color: var(--text-2); margin-bottom: 5px;">${sender}</div>
<div style="white-space: pre-wrap;">${text}</div>
`;
messagesDiv.appendChild(messageDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// Improved AI Analysis with streaming
async function runAIAnalysis() {
if (!currentSurveyId) {
alert('Please select a survey first');
return;
}
const modelSelect = document.getElementById('analyzeModelSelect');
const selectedModel = modelSelect.value || defaultModel;
document.getElementById('analyzeSpinner').style.display = 'inline-block';
document.getElementById('analysisResult').style.display = 'block';
document.getElementById('analysisContent').textContent = 'Generating analysis...';
try {
const response = await fetch(`/api/surveys/${currentSurveyId}/analyze`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({model: selectedModel})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let analysisText = '';
while (true) {
const {done, value} = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.trim()) {
try {
const data = JSON.parse(line);
if (data.response) {
analysisText += data.response;
document.getElementById('analysisContent').textContent = analysisText;
}
if (data.done) {
document.getElementById('analysisExport').style.display = 'block';
document.getElementById('chatInterface').style.display = 'block';
// Clear chat history for new analysis
chatHistory = [];
document.getElementById('chatMessages').innerHTML = '';
appendChatMessage('AI', 'Analysis complete! You can now ask questions about the recommendations.', 'assistant');
}
} catch (e) {
// Ignore parse errors for incomplete chunks
}
}
}
}
} catch (e) {
console.error('Analysis error:', e);
document.getElementById('analysisContent').textContent = 'Error: ' + e.message;
} finally {
document.getElementById('analyzeSpinner').style.display = 'none';
}
}
// Load models
async function loadModels() {
try {
@@ -890,14 +1038,31 @@
defaultModel = data.default || 'llava:latest';
const analyzeSelect = document.getElementById('analyzeModelSelect');
analyzeSelect.innerHTML = availableModels.map(m =>
const createSelect = document.getElementById('createModelSelect');
const options = availableModels.map(m =>
`<option value="${m}" ${m === defaultModel ? 'selected' : ''}>${m}</option>`
).join('');
analyzeSelect.innerHTML = options;
createSelect.innerHTML = options;
} catch (e) {
console.error('Failed to load models:', e);
}
}
// Handle Enter key in chat
document.addEventListener('DOMContentLoaded', () => {
const chatInput = document.getElementById('chatInput');
if (chatInput) {
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendChatMessage();
}
});
}
});
</script>
</body>
</html>