Add chat endpoint, model selector, and improved AI analysis
This commit is contained in:
+166
-1
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user