#!/usr/bin/env python3
"""Script to add conversation interface to existing index.html"""
# CSS for chat modal
CHAT_CSS = """
/* Chat Modal Styles */
.chat-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 10000;
justify-content: center;
align-items: center;
}
.chat-modal.active {
display: flex;
}
.chat-container {
background: white;
border-radius: 20px;
width: 90%;
max-width: 600px;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.chat-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 20px 20px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header h3 {
margin: 0;
font-size: 1.3em;
}
.chat-progress {
background: rgba(255,255,255,0.3);
height: 8px;
border-radius: 4px;
margin-top: 10px;
overflow: hidden;
}
.chat-progress-bar {
background: #4ade80;
height: 100%;
transition: width 0.3s;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 20px;
min-height: 200px;
max-height: 400px;
}
.chat-message {
margin-bottom: 15px;
display: flex;
gap: 10px;
animation: slideIn 0.3s;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.chat-message.user {
justify-content: flex-end;
}
.chat-message .avatar {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
flex-shrink: 0;
}
.chat-message.assistant .avatar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.chat-message.user .avatar {
background: #e2e8f0;
}
.chat-bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
word-wrap: break-word;
}
.chat-message.assistant .chat-bubble {
background: #f1f5f9;
color: #1e293b;
}
.chat-message.user .chat-bubble {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.chat-input-area {
padding: 20px;
border-top: 1px solid #e2e8f0;
display: flex;
gap: 10px;
}
.chat-input {
flex: 1;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 24px;
font-size: 15px;
outline: none;
transition: border-color 0.3s;
}
.chat-input:focus {
border-color: #667eea;
}
.chat-send-btn {
padding: 12px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 24px;
cursor: pointer;
font-weight: 600;
transition: transform 0.2s;
}
.chat-send-btn:hover:not(:disabled) {
transform: scale(1.05);
}
.chat-send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.chat-loading {
display: flex;
gap: 5px;
padding: 10px;
}
.chat-loading span {
width: 8px;
height: 8px;
background: #94a3b8;
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.chat-loading span:nth-child(1) { animation-delay: -0.32s; }
.chat-loading span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
"""
# HTML for chat modal
CHAT_HTML = """
<!-- Chat Modal -->
<div id="chatModal" class="chat-modal">
<div class="chat-container">
<div class="chat-header">
<div style="flex: 1;">
<h3>Let's gather some more details</h3>
<div class="chat-progress">
<div id="chatProgressBar" class="chat-progress-bar" style="width: 0%"></div>
</div>
</div>
</div>
<div id="chatMessages" class="chat-messages">
<!-- Messages will be added here dynamically -->
</div>
<div class="chat-input-area">
<input type="text" id="chatInput" class="chat-input" placeholder="Type your answer..."
onkeypress="if(event.key === 'Enter') sendChatMessage()">
<button onclick="sendChatMessage()" id="chatSendBtn" class="chat-send-btn">Send</button>
</div>
</div>
</div>
"""
# JavaScript for conversation handling - will be inserted at the end of existing script
CHAT_JS = """
// Conversation state
let conversationState = {
sessionId: null,
messages: [],
isActive: false
};
// Start conversation flow
async function startConversation() {
const query = document.getElementById('queryInput').value.trim();
const dauInput = document.getElementById('dauInput').value;
const dauUnit = parseInt(document.getElementById('dauUnit').value);
const dau = dauInput ? parseInt(dauInput) * dauUnit : null;
// Build initial message with context from form
let initialMessage = query;
if (dau) {
initialMessage += ` with ${dau.toLocaleString()} daily active users`;
}
try {
const response = await fetch('/conversation/start', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ initial_message: initialMessage })
});
const data = await response.json();
conversationState.sessionId = data.session_id;
conversationState.isActive = true;
// Show chat modal
document.getElementById('chatModal').classList.add('active');
// Add initial messages
addChatMessage('user', initialMessage);
addChatMessage('assistant', data.question);
// Update progress
updateChatProgress(data.completion_percentage);
// Focus input
document.getElementById('chatInput').focus();
} catch (error) {
console.error('Failed to start conversation:', error);
// Fall back to direct recommendation
await getRecommendationDirect();
}
}
// Send chat message
async function sendChatMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
if (!message || !conversationState.isActive) return;
// Disable input
input.disabled = true;
document.getElementById('chatSendBtn').disabled = true;
// Add user message
addChatMessage('user', message);
input.value = '';
// Show loading
const loadingDiv = document.createElement('div');
loadingDiv.className = 'chat-message assistant';
loadingDiv.id = 'chatLoading';
loadingDiv.innerHTML = `
<div class="avatar">🤖</div>
<div class="chat-loading">
<span></span><span></span><span></span>
</div>
`;
document.getElementById('chatMessages').appendChild(loadingDiv);
scrollChatToBottom();
try {
const response = await fetch('/conversation/message', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
session_id: conversationState.sessionId,
message: message
})
});
const data = await response.json();
// Remove loading
document.getElementById('chatLoading')?.remove();
// Update progress
updateChatProgress(data.completion_percentage);
// Check if ready for recommendation
if (data.ready_for_recommendation) {
addChatMessage('assistant', 'Perfect! I have all the information I need. Generating your recommendations...');
setTimeout(async () => {
// Close chat modal
document.getElementById('chatModal').classList.remove('active');
// Generate recommendation using extracted context
await generateRecommendationFromContext(data.extracted_context);
}, 1500);
} else {
// Add next question
addChatMessage('assistant', data.question);
// Re-enable input
input.disabled = false;
document.getElementById('chatSendBtn').disabled = false;
input.focus();
}
} catch (error) {
console.error('Failed to send message:', error);
document.getElementById('chatLoading')?.remove();
addChatMessage('assistant', 'Sorry, something went wrong. Generating recommendations with current information...');
setTimeout(() => {
document.getElementById('chatModal').classList.remove('active');
getRecommendationDirect();
}, 1500);
}
}
// Add chat message to UI
function addChatMessage(role, content) {
const messagesDiv = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${role}`;
const avatar = role === 'assistant' ? '🤖' : '👤';
messageDiv.innerHTML = `
<div class="avatar">${avatar}</div>
<div class="chat-bubble">${content}</div>
`;
messagesDiv.appendChild(messageDiv);
scrollChatToBottom();
}
// Update progress bar
function updateChatProgress(percentage) {
document.getElementById('chatProgressBar').style.width = percentage + '%';
}
// Scroll chat to bottom
function scrollChatToBottom() {
const messagesDiv = document.getElementById('chatMessages');
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
// Generate recommendation from extracted context
async function generateRecommendationFromContext(context) {
console.log('Generating recommendation from context:', context);
// Show loading
document.getElementById('loading').style.display = 'block';
document.getElementById('results').style.display = 'none';
document.getElementById('error').style.display = 'none';
document.getElementById('submitBtn').disabled = true;
// Build query from context
let query = document.getElementById('queryInput').value.trim();
// Build request
const requestBody = { query };
// Add DAU if available in context
if (context.dau) {
requestBody.dau = context.dau;
}
// Add API key if user provided one
const ownRadio = document.getElementById('ownRadio');
const userApiKey = document.getElementById('userApiKey').value.trim();
if (ownRadio.checked && userApiKey) {
requestBody.api_key = userApiKey;
}
try {
const response = await fetch('/recommend', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
const data = await response.json();
currentData = data;
if (data.status === 'error') {
throw new Error(data.error || 'Unknown error occurred');
}
displayResults(data);
} catch (error) {
console.error('Error:', error);
const errorEl = document.getElementById('error');
errorEl.textContent = `Error: ${error.message}`;
errorEl.style.display = 'block';
} finally {
document.getElementById('loading').style.display = 'none';
document.getElementById('submitBtn').disabled = false;
}
}
// Direct recommendation (fallback)
async function getRecommendationDirect() {
// This is the original function renamed
return getRecommendationOriginal();
}
"""
def main():
input_file = '/Users/admin/codeprojects/tech-stack-advisor/backend/static/index.html'
output_file = input_file
print("Reading original HTML...")
with open(input_file, 'r', encoding='utf-8') as f:
html_content = f.read()
print("Making modifications...")
# 1. Add chat CSS before </style>
html_content = html_content.replace(' </style>', CHAT_CSS + '\n </style>')
# 2. Add chat HTML before </body>
html_content = html_content.replace('</body>', CHAT_HTML + '\n</body>')
# 3. Rename existing getRecommendation to getRecommendationOriginal
html_content = html_content.replace(
'async function getRecommendation()',
'async function getRecommendationOriginal()'
)
# 4. Add new getRecommendation that starts conversation
new_get_recommendation = """
async function getRecommendation() {
console.log('getRecommendation() called - v3.0 with conversation');
const query = document.getElementById('queryInput').value.trim();
if (!query) {
alert('Please enter a project description');
return;
}
// Start conversation flow
await startConversation();
}
"""
# Insert new getRecommendation before getRecommendationOriginal
html_content = html_content.replace(
'async function getRecommendationOriginal()',
new_get_recommendation + '\n async function getRecommendationOriginal()'
)
# 5. Add conversation handling JS before closing </script>
# Find the last </script> tag
last_script_pos = html_content.rfind('</script>')
if last_script_pos != -1:
html_content = html_content[:last_script_pos] + CHAT_JS + '\n ' + html_content[last_script_pos:]
print("Writing modified HTML...")
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_content)
print("✓ Successfully updated index.html with conversation interface!")
print("Backup saved as index.html.backup")
if __name__ == '__main__':
main()