From b552f27f52ff3015f84173335bbc344b8e8702c2 Mon Sep 17 00:00:00 2001 From: JC Beasley Date: Thu, 2 Jul 2026 15:52:48 -0700 Subject: [PATCH] Add chat endpoint, model selector, and improved AI analysis --- app.py | 35 +++++++++++ index.html | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 201 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 7715876..3b28ffc 100644 --- a/app.py +++ b/app.py @@ -966,6 +966,41 @@ Report generated by IT Site Survey AI
return jsonify({'email_body': email_body}) +@app.route("/api/surveys//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__': port = int(os.environ.get('PORT', 3003)) print("───────────────────────────────────────") diff --git a/index.html b/index.html index d06d00a..93a2edd 100644 --- a/index.html +++ b/index.html @@ -334,6 +334,13 @@ +
+ + +
+
@@ -392,6 +399,18 @@
+ + + @@ -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 = ` +
${sender}
+
${text}
+ `; + 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 => `` ).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(); + } + }); + } + });