Add chat endpoint, model selector, and improved AI analysis
This commit is contained in:
@@ -966,6 +966,41 @@ Report generated by IT Site Survey AI<br>
|
|||||||
return jsonify({'email_body': email_body})
|
return jsonify({'email_body': email_body})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/surveys/<survey_id>/chat", methods=["POST"])
|
||||||
|
def chat_with_ai(survey_id):
|
||||||
|
survey = surveys_db.get(survey_id)
|
||||||
|
if not survey:
|
||||||
|
return jsonify({"error": "Survey not found"}), 404
|
||||||
|
|
||||||
|
data = request.get_json() or {}
|
||||||
|
user_message = data.get("message", "")
|
||||||
|
chat_history = data.get("history", [])
|
||||||
|
model = data.get("model", DEFAULT_MODEL)
|
||||||
|
|
||||||
|
latest_response = survey["responses"][-1] if survey["responses"] else None
|
||||||
|
responses = latest_response["responses"] if latest_response else {}
|
||||||
|
|
||||||
|
survey_context = f"Client: {survey.get("client_name", "Unknown")}\nSite: {survey.get("site_name", "Unknown")}\n\nSurvey Responses:\n"
|
||||||
|
for q in survey["questions"]:
|
||||||
|
qid = q["id"]
|
||||||
|
answer = responses.get(qid, "Not answered")
|
||||||
|
survey_context += f"{q["question"]}: {answer}\n"
|
||||||
|
|
||||||
|
messages = [{"role": "system", "content": f"You are a network infrastructure expert reviewing this survey: {survey_context}"}]
|
||||||
|
for msg in chat_history:
|
||||||
|
messages.append(msg)
|
||||||
|
messages.append({"role": "user", "content": user_message})
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = requests.post(f"{OLLAMA_BASE}/api/chat", json={"model": model, "messages": messages, "stream": False}, timeout=120)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
result = resp.json()
|
||||||
|
return jsonify({"response": result.get("message", {}).get("content", "No response")})
|
||||||
|
else:
|
||||||
|
return jsonify({"error": f"Ollama error: {resp.status_code}"}), 500
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
port = int(os.environ.get('PORT', 3003))
|
port = int(os.environ.get('PORT', 3003))
|
||||||
print("───────────────────────────────────────")
|
print("───────────────────────────────────────")
|
||||||
|
|||||||
+166
-1
@@ -334,6 +334,13 @@
|
|||||||
<textarea id="surveyDescription" placeholder="Brief description of the survey purpose..."></textarea>
|
<textarea id="surveyDescription" placeholder="Brief description of the survey purpose..."></textarea>
|
||||||
</div>
|
</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 id="surveyQuestions"></div>
|
||||||
|
|
||||||
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--border);">
|
<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>
|
<button class="btn" onclick="exportResults('email')">📧 Email Results</button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</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
|
// Load models
|
||||||
async function loadModels() {
|
async function loadModels() {
|
||||||
try {
|
try {
|
||||||
@@ -890,14 +1038,31 @@
|
|||||||
defaultModel = data.default || 'llava:latest';
|
defaultModel = data.default || 'llava:latest';
|
||||||
|
|
||||||
const analyzeSelect = document.getElementById('analyzeModelSelect');
|
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>`
|
`<option value="${m}" ${m === defaultModel ? 'selected' : ''}>${m}</option>`
|
||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
|
analyzeSelect.innerHTML = options;
|
||||||
|
createSelect.innerHTML = options;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load models:', 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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user