Tech Stack Advisor - Code Viewer

← Back to File Tree

update_frontend.py

Language: python | Path: backend/static/update_frontend.py | Lines: 491
#!/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()