{% extends "appbuilder/base.html" %} {% block head_css %} {{ super() }} {% endblock %} {% block content %}
{{ content|safe }}
{% endblock %} {% block tail %} {{ super() }} let currentSessionId = null; let csrfToken = null; // Expandable Code Blocks Functions function toggleCodeBlock(header) { const content = header.nextElementSibling; const isExpanded = content.classList.contains('expanded'); console.log('Toggling code block, currently expanded:', isExpanded); if (isExpanded) { // Collapse content.classList.remove('expanded'); header.classList.remove('expanded'); content.style.maxHeight = '0'; console.log('Collapsed'); } else { // Expand content.classList.add('expanded'); header.classList.add('expanded'); // Set a reasonable max height for the content content.style.maxHeight = '1000px'; console.log('Expanded, content height:', content.scrollHeight); } } // Make functions globally available window.toggleCodeBlock = toggleCodeBlock; document.addEventListener('DOMContentLoaded', function() { initializeCSRF().then(() => { initializeSession(); }); document.getElementById('chatInputForm').addEventListener('submit', sendMessage); document.getElementById('newChatBtn').addEventListener('click', newChat); document.getElementById('clearChatBtn').addEventListener('click', clearChat); document.getElementById('showExamplesBtn').addEventListener('click', showExamples); }); async function initializeCSRF() { try { const response = await fetch('/api/v1/security/csrf_token/'); if (response.ok) { const data = await response.json(); csrfToken = data.result; } } catch (error) { console.error('Error fetching CSRF token:', error); } } async function initializeSession() { try { const headers = { 'Content-Type': 'application/json' }; if (csrfToken) { headers['X-CSRFToken'] = csrfToken; } const response = await fetch('/aisupersetassistantview/api/new_session', { method: 'POST', headers: headers }); if (response.ok) { const data = await response.json(); currentSessionId = data.session_id; document.getElementById('sessionId').textContent = currentSessionId.substring(0, 8) + '...'; document.getElementById('connectionStatus').textContent = 'Connected'; } else { throw new Error('Failed to initialize session'); } } catch (error) { console.error('Error initializing session:', error); document.getElementById('connectionStatus').textContent = 'Error'; } } async function sendMessage(event) { event.preventDefault(); const messageInput = document.getElementById('messageInput'); const sendBtn = document.getElementById('sendBtn'); const message = messageInput.value.trim(); if (!message || !currentSessionId) return; messageInput.disabled = true; sendBtn.disabled = true; sendBtn.textContent = 'Sending...'; addMessage(message, 'user'); messageInput.value = ''; showTypingIndicator(); try { let assistantMessageDiv = null; let assistantContentDiv = null; function createAssistantMessage() { const messagesContainer = document.getElementById('chatMessages'); assistantMessageDiv = document.createElement('div'); assistantMessageDiv.className = 'message assistant'; assistantContentDiv = document.createElement('div'); assistantContentDiv.innerHTML = ''; const timeDiv = document.createElement('div'); timeDiv.className = 'message-time'; timeDiv.textContent = new Date().toLocaleTimeString(); assistantMessageDiv.appendChild(assistantContentDiv); assistantMessageDiv.appendChild(timeDiv); messagesContainer.appendChild(assistantMessageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } const headers = { 'Content-Type': 'application/json' }; if (csrfToken) { headers['X-CSRFToken'] = csrfToken; } const response = await fetch('/aisupersetassistantview/api/chat_stream', { method: 'POST', headers: headers, body: JSON.stringify({ message: message, session_id: currentSessionId }) }); if (!response.ok) { throw new Error('Failed to send message'); } hideTypingIndicator(); createAssistantMessage(); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); if (data.type === 'session' && data.session_id) { currentSessionId = data.session_id; document.getElementById('sessionId').textContent = currentSessionId.substring(0, 8) + '...'; } else if (data.type === 'chunk' && data.content) { assistantContentDiv.innerHTML += data.content; const messagesContainer = document.getElementById('chatMessages'); messagesContainer.scrollTop = messagesContainer.scrollHeight; } else if (data.type === 'error') { assistantContentDiv.innerHTML = data.content; } else if (data.type === 'done') { // First, convert tool blocks BEFORE general markdown processing let finalContent = assistantContentDiv.innerHTML; console.log('Processing final content, length:', finalContent.length); console.log('Content preview:', finalContent.substring(0, 300)); // Convert tool execution blocks to expandable format // Try both patterns: with \n\n at start and without (for first tool) const toolPattern1 = /\n\nStart Running Tool:\n```\n([\s\S]*?)```\n\nTool Output:\n```\n([\s\S]*?)```\n/g; const toolPattern2 = /Start Running Tool:\n```\n([\s\S]*?)```\n\nTool Output:\n```\n([\s\S]*?)```\n/g; let matchCount = 0; // First try pattern with leading newlines (for consecutive tools) finalContent = finalContent.replace(toolPattern1, function(match, toolData, outputData) { matchCount++; console.log('Match found (pattern 1) #' + matchCount); return `${toolData.trim()}${outputData.trim()}`; }); // Then try pattern without leading newlines (for first tool) finalContent = finalContent.replace(toolPattern2, function(match, toolData, outputData) { matchCount++; console.log('Match found (pattern 2) #' + matchCount); return `${toolData.trim()}${outputData.trim()}`; }); console.log('Total tool blocks found:', matchCount); // Now do standard markdown conversion finalContent = finalContent .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/```([\s\S]*?)```/g, '
$1
') .replace(/`([^`]+)`/g, '$1') .replace(/\n/g, '
'); // Finally, convert tool block placeholders to expandable HTML finalContent = finalContent.replace(/([\s\S]*?)([\s\S]*?)/g, function(match, toolData, outputData) { const cleanToolData = toolData.replace(/
/g, '\n').replace(/</g, '<').replace(/>/g, '>'); const cleanOutputData = outputData.replace(/
/g, '\n').replace(/</g, '<').replace(/>/g, '>'); return `
${cleanToolData}
Tool Output:
${cleanOutputData}

`; }); assistantContentDiv.innerHTML = finalContent; console.log('Final content set'); break; } } catch (e) { console.error('Error parsing streaming data:', e); } } } } } catch (error) { console.error('Error sending message:', error); hideTypingIndicator(); addMessage('Sorry, I encountered an error. Please try again.', 'assistant'); } finally { messageInput.disabled = false; sendBtn.disabled = false; sendBtn.textContent = 'Send'; messageInput.focus(); } } function addMessage(content, sender) { const messagesContainer = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = `message ${sender}`; let formattedContent = content; // For assistant messages, process tool blocks first if (sender === 'assistant') { // Convert tool execution blocks to expandable format // Try both patterns: with \n\n at start and without (for first tool) const toolPattern1 = /\n\nStart Running Tool:\n```\n([\s\S]*?)```\n\nTool Output:\n```\n([\s\S]*?)```\n/g; const toolPattern2 = /Start Running Tool:\n```\n([\s\S]*?)```\n\nTool Output:\n```\n([\s\S]*?)```\n/g; formattedContent = formattedContent.replace(toolPattern1, function(match, toolData, outputData) { return `${toolData.trim()}${outputData.trim()}`; }); formattedContent = formattedContent.replace(toolPattern2, function(match, toolData, outputData) { return `${toolData.trim()}${outputData.trim()}`; }); } // Now do standard markdown conversion formattedContent = formattedContent .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/```([\s\S]*?)```/g, '
$1
') .replace(/`([^`]+)`/g, '$1') .replace(/\n/g, '
'); // Convert tool block placeholders to expandable HTML if (sender === 'assistant') { formattedContent = formattedContent.replace(/([\s\S]*?)([\s\S]*?)/g, function(match, toolData, outputData) { const cleanToolData = toolData.replace(/
/g, '\n').replace(/</g, '<').replace(/>/g, '>'); const cleanOutputData = outputData.replace(/
/g, '\n').replace(/</g, '<').replace(/>/g, '>'); return `
${cleanToolData}
Tool Output:
${cleanOutputData}

`; }); } messageDiv.innerHTML = `
${formattedContent}
${new Date().toLocaleTimeString()}
`; messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function showTypingIndicator() { document.getElementById('typingIndicator').classList.add('show'); const messagesContainer = document.getElementById('chatMessages'); messagesContainer.scrollTop = messagesContainer.scrollHeight; } function hideTypingIndicator() { document.getElementById('typingIndicator').classList.remove('show'); } async function newChat() { if (confirm('Start a new chat session? This will clear the current conversation.')) { await initializeSession(); clearChatDisplay(); addMessage('Hello! I\'m your AI Superset Assistant 🚀\n\nI can help you with:\n• Creating and structuring DAGs\n• Debugging workflow issues\n• Performance optimization\n• Best practices and patterns\n\nWhat would you like to know about Apache Superset?', 'assistant'); } } function clearChat() { if (confirm('Clear the current chat history?')) { clearChatDisplay(); addMessage('Chat cleared. How can I help you with Apache Superset?', 'assistant'); } } function clearChatDisplay() { const messagesContainer = document.getElementById('chatMessages'); messagesContainer.innerHTML = ''; } function showExamples() { const examples = [ "How do I create a simple DAG?", "Help me debug a failing task", "What are DAG best practices?", "How can I optimize my workflow performance?", "Show me sensor examples" ]; const messageInput = document.getElementById('messageInput'); const randomExample = examples[Math.floor(Math.random() * examples.length)]; messageInput.value = randomExample; messageInput.focus(); } document.getElementById('messageInput').addEventListener('keypress', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.querySelector('.chat-input-form').dispatchEvent(new Event('submit')); } }); {% endblock %}