// Tutorial-specific JavaScript functionality let currentTutorial = null; let currentStep = 0; let totalSteps = 0; /** * Initialize tutorial functionality */ function initializeTutorial(tutorialData) { currentTutorial = tutorialData; currentStep = tutorialData.currentStep || 0; totalSteps = tutorialData.totalSteps; setupTutorialNavigation(); setupStepTracking(); setupKeyboardNavigation(); updateProgress(); // Show the current step showStep(currentStep); } /** * Setup tutorial navigation */ function setupTutorialNavigation() { // Step navigation links document.querySelectorAll('.step-nav-link').forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const stepIndex = parseInt(this.dataset.step); navigateToStep(stepIndex); }); }); // Next/Previous buttons document.querySelectorAll('[onclick*="navigateToStep"]').forEach(btn => { btn.addEventListener('click', function(e) { e.preventDefault(); const match = this.getAttribute('onclick').match(/navigateToStep\((\d+)\)/); if (match) { navigateToStep(parseInt(match[1])); } }); }); } /** * Setup step tracking */ function setupStepTracking() { // Track time spent on each step let stepStartTime = Date.now(); window.addEventListener('beforeunload', () => { const timeSpent = Math.floor((Date.now() - stepStartTime) / 1000); trackStepTime(currentStep, timeSpent); }); // Track step completion document.addEventListener('visibilitychange', () => { if (document.hidden) { const timeSpent = Math.floor((Date.now() - stepStartTime) / 1000); trackStepTime(currentStep, timeSpent); } else { stepStartTime = Date.now(); } }); } /** * Setup keyboard navigation */ function setupKeyboardNavigation() { document.addEventListener('keydown', function(e) { // Only handle keyboard navigation when tutorial content is focused if (!document.querySelector('.tutorial-content').contains(document.activeElement)) { return; } switch(e.key) { case 'ArrowLeft': if (currentStep > 0) { e.preventDefault(); navigateToStep(currentStep - 1); } break; case 'ArrowRight': if (currentStep < totalSteps - 1) { e.preventDefault(); navigateToStep(currentStep + 1); } break; case 'Home': e.preventDefault(); navigateToStep(0); break; case 'End': e.preventDefault(); navigateToStep(totalSteps - 1); break; } }); } /** * Navigate to a specific step */ function navigateToStep(stepIndex) { if (stepIndex < 0 || stepIndex >= totalSteps) { return; } // Hide current step const currentStepElement = document.querySelector(`#step-${currentStep}`); if (currentStepElement) { currentStepElement.classList.remove('active'); } // Update step navigation document.querySelectorAll('.step-nav-link').forEach(link => { link.classList.remove('active'); }); // Show new step currentStep = stepIndex; showStep(currentStep); // Update navigation const newNavLink = document.querySelector(`[data-step="${currentStep}"]`); if (newNavLink) { newNavLink.classList.add('active'); } // Update progress updateProgress(); // Save progress saveProgress(); // Track step view window.DifyLearning?.trackEvent('tutorial_step_viewed', { tutorial_id: currentTutorial.id, step: currentStep + 1, step_title: document.querySelector(`#step-${currentStep} .step-title`)?.textContent }); // Scroll to top of content const tutorialContent = document.querySelector('.tutorial-content'); if (tutorialContent) { tutorialContent.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } /** * Show a specific step */ function showStep(stepIndex) { // Hide all steps document.querySelectorAll('.tutorial-step').forEach(step => { step.classList.remove('active'); }); // Show target step const targetStep = document.querySelector(`#step-${stepIndex}`); if (targetStep) { targetStep.classList.add('active'); // Initialize any interactive elements in this step initializeStepElements(targetStep); // Add fade-in animation targetStep.classList.add('fade-in'); // Update step counter updateStepCounter(stepIndex + 1); } } /** * Initialize interactive elements in a step */ function initializeStepElements(stepElement) { // Initialize code examples with syntax highlighting const codeBlocks = stepElement.querySelectorAll('pre code'); codeBlocks.forEach(block => { // Add copy button addCopyButton(block); // Add line numbers if needed if (block.textContent.split('\n').length > 5) { addLineNumbers(block); } }); // Initialize interactive demos const demos = stepElement.querySelectorAll('.interactive-demo'); demos.forEach(demo => { initializeDemo(demo); }); // Initialize tooltips for this step const tooltips = stepElement.querySelectorAll('[data-bs-toggle="tooltip"]'); tooltips.forEach(tooltip => { new bootstrap.Tooltip(tooltip); }); } /** * Add copy button to code blocks */ function addCopyButton(codeBlock) { if (codeBlock.querySelector('.copy-button')) { return; // Already has copy button } const copyButton = document.createElement('button'); copyButton.className = 'btn btn-sm btn-outline-secondary copy-button position-absolute top-0 end-0 m-2'; copyButton.innerHTML = ''; copyButton.title = 'Copy code'; copyButton.addEventListener('click', async () => { try { await navigator.clipboard.writeText(codeBlock.textContent); copyButton.innerHTML = ''; copyButton.classList.add('btn-success'); copyButton.classList.remove('btn-outline-secondary'); setTimeout(() => { copyButton.innerHTML = ''; copyButton.classList.remove('btn-success'); copyButton.classList.add('btn-outline-secondary'); feather.replace(); }, 2000); window.DifyLearning?.showToast('Code copied to clipboard!', 'success'); } catch (err) { window.DifyLearning?.showToast('Failed to copy code', 'danger'); } }); // Make parent relative positioned const pre = codeBlock.closest('pre'); if (pre) { pre.style.position = 'relative'; pre.appendChild(copyButton); feather.replace(); } } /** * Add line numbers to code blocks */ function addLineNumbers(codeBlock) { const lines = codeBlock.textContent.split('\n'); const lineNumbers = lines.map((_, index) => index + 1).join('\n'); const lineNumbersElement = document.createElement('pre'); lineNumbersElement.className = 'line-numbers'; lineNumbersElement.textContent = lineNumbers; lineNumbersElement.style.cssText = ` position: absolute; left: 0; top: 0; padding: 1rem 0.5rem; background: #f1f3f4; color: #666; font-size: 0.8rem; border-right: 1px solid #ddd; user-select: none; width: 3rem; text-align: right; `; const pre = codeBlock.closest('pre'); if (pre) { pre.style.position = 'relative'; pre.style.paddingLeft = '4rem'; pre.appendChild(lineNumbersElement); } } /** * Initialize interactive demos */ function initializeDemo(demoElement) { const demoType = demoElement.dataset.demoType; switch(demoType) { case 'workflow-builder': initializeWorkflowDemo(demoElement); break; case 'chatbot-preview': initializeChatbotDemo(demoElement); break; case 'agent-configuration': initializeAgentDemo(demoElement); break; default: // Generic demo initialization demoElement.innerHTML = `

Interactive Demo

`; feather.replace(); } } /** * Initialize workflow builder demo */ function initializeWorkflowDemo(element) { element.innerHTML = `
Start
LLM
Response
`; feather.replace(); } /** * Initialize chatbot demo */ function initializeChatbotDemo(element) { element.innerHTML = `
Hello! I'm your Dify AI assistant. How can I help you today?
`; feather.replace(); } /** * Handle demo chat input */ function handleDemoChatInput(event) { if (event.key === 'Enter') { sendDemoMessage(); } } /** * Send demo message */ function sendDemoMessage() { const input = event.target.closest('.chat-input').querySelector('input'); const message = input.value.trim(); if (!message) return; const messagesContainer = document.getElementById('demo-chat-messages'); // Add user message const userMessage = document.createElement('div'); userMessage.className = 'message user-message'; userMessage.innerHTML = `
${message}
`; messagesContainer.appendChild(userMessage); // Clear input input.value = ''; // Simulate bot response setTimeout(() => { const botMessage = document.createElement('div'); botMessage.className = 'message bot-message'; botMessage.innerHTML = `
That's a great question about Dify! This is a demo response showing how your chatbot would interact with users.
`; messagesContainer.appendChild(botMessage); messagesContainer.scrollTop = messagesContainer.scrollHeight; }, 1000); messagesContainer.scrollTop = messagesContainer.scrollHeight; } /** * Update progress display */ function updateProgress() { // Update progress bar const progressBar = document.getElementById('tutorial-progress-bar'); if (progressBar) { const percentage = ((currentStep + 1) / totalSteps) * 100; window.DifyLearning?.animateProgressBar(progressBar, percentage); } // Update step counter updateStepCounter(currentStep + 1); // Update sidebar navigation updateSidebarNavigation(); } /** * Update step counter */ function updateStepCounter(stepNumber) { const counter = document.getElementById('current-step'); if (counter) { counter.textContent = stepNumber; } } /** * Update sidebar navigation */ function updateSidebarNavigation() { document.querySelectorAll('.step-nav-link').forEach((link, index) => { link.classList.remove('active'); if (index === currentStep) { link.classList.add('active'); } // Remove completed indicators (checkmarks disabled) const existingCheck = link.querySelector('i[data-feather="check"]'); if (existingCheck) { existingCheck.remove(); } }); } /** * Save progress to server */ async function saveProgress() { if (!currentTutorial) return; try { const response = await fetch('/api/progress/update', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ tutorial_id: currentTutorial.id, current_step: currentStep, completed: false }) }); if (!response.ok) { throw new Error('Failed to save progress'); } } catch (error) { console.error('Error saving progress:', error); // Show error message to user window.DifyLearning?.showToast('Failed to save progress', 'warning'); } } /** * Complete tutorial */ async function completeTutorial() { if (!currentTutorial) return; try { const response = await fetch('/api/progress/update', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ tutorial_id: currentTutorial.id, current_step: totalSteps - 1, completed: true }) }); if (response.ok) { // Track completion window.DifyLearning?.trackEvent('tutorial_completed', { tutorial_id: currentTutorial.id, total_steps: totalSteps, completion_time: new Date().toISOString() }); // Show completion modal const modal = new bootstrap.Modal(document.getElementById('completionModal')); modal.show(); // Update UI to show completion document.querySelectorAll('.progress-circle').forEach(circle => { circle.className = 'progress-circle completed'; circle.innerHTML = ''; }); feather.replace(); } else { throw new Error('Failed to complete tutorial'); } } catch (error) { console.error('Error completing tutorial:', error); window.DifyLearning?.showToast('Failed to save completion status', 'warning'); } } /** * Track time spent on step */ function trackStepTime(stepIndex, seconds) { if (seconds < 5) return; // Ignore very short durations const stepData = { tutorial_id: currentTutorial?.id, step: stepIndex + 1, time_spent: seconds }; // Store locally for analytics const stepTimes = JSON.parse(localStorage.getItem('dify_step_times') || '[]'); stepTimes.push({ ...stepData, timestamp: new Date().toISOString() }); localStorage.setItem('dify_step_times', JSON.stringify(stepTimes.slice(-1000))); } /** * Add workflow node (demo function) */ function addWorkflowNode(button) { const canvas = button.closest('.workflow-demo').querySelector('.workflow-canvas'); const newNode = document.createElement('div'); newNode.className = 'workflow-node'; newNode.innerHTML = ` New Node `; // Add arrow before new node const arrow = document.createElement('div'); arrow.className = 'workflow-arrow'; arrow.textContent = '→'; // Insert before end node const endNode = canvas.querySelector('.end-node'); canvas.insertBefore(arrow, endNode); canvas.insertBefore(newNode, endNode); feather.replace(); // Show success message window.DifyLearning?.showToast('Node added to workflow!', 'success'); } // Make functions available globally window.navigateToStep = navigateToStep; window.completeTutorial = completeTutorial; window.handleDemoChatInput = handleDemoChatInput; window.sendDemoMessage = sendDemoMessage; window.addWorkflowNode = addWorkflowNode;