// Import all components import { TypingIndicator } from '/static/assets/js/components/TypingIndicator.js'; import { FormField } from '/static/assets/js/components/FormField.js'; import { DynamicForm } from '/static/assets/js/components/DynamicForm.js'; import { ChatMessage } from '/static/assets/js/components/ChatMessage.js'; import { MessageHistory } from '/static/assets/js/components/MessageHistory.js'; import { ChatInput } from '/static/assets/js/components/ChatInput.js'; import { ProgressTracker } from '/static/assets/js/components/ProgressTracker.js'; // Main Chat Application export const ChatApp = { name: 'ChatApp', components: { TypingIndicator, FormField, DynamicForm, ChatMessage, MessageHistory, ChatInput }, data() { // Maak een lokale kopie van de chatConfig om undefined errors te voorkomen const chatConfig = window.chatConfig || {}; const settings = chatConfig.settings || {}; return { // Base template data (keeping existing functionality) explanation: chatConfig.explanation || '', // Chat-specific data currentMessage: '', allMessages: [], isTyping: false, isLoading: false, isSubmittingForm: false, messageIdCounter: 1, formValues: {}, // API prefix voor endpoints apiPrefix: chatConfig.apiPrefix || '', // Configuration from Flask/server conversationId: chatConfig.conversationId || 'default', userId: chatConfig.userId || null, userName: chatConfig.userName || '', // Settings met standaard waarden en overschreven door server config settings: { maxMessageLength: settings.maxMessageLength || 2000, allowFileUpload: settings.allowFileUpload === true, allowVoiceMessage: settings.allowVoiceMessage === true, autoScroll: settings.autoScroll === true }, // UI state isMobile: window.innerWidth <= 768, showSidebar: window.innerWidth > 768, // Advanced features messageSearch: '', filteredMessages: [], isSearching: false }; }, computed: { // Keep existing computed from base.html compiledExplanation() { if (typeof marked === 'function') { return marked(this.explanation); } else if (marked && typeof marked.parse === 'function') { return marked.parse(this.explanation); } else { console.error('Marked library not properly loaded'); return this.explanation; } }, displayMessages() { return this.isSearching ? this.filteredMessages : this.allMessages; }, hasMessages() { return this.allMessages.length > 0; } }, mounted() { this.initializeChat(); this.setupEventListeners(); }, beforeUnmount() { this.cleanup(); }, methods: { // Initialization initializeChat() { console.log('Initializing chat application...'); // Load historical messages from server this.loadHistoricalMessages(); // Add welcome message if no history if (this.allMessages.length === 0) { this.addWelcomeMessage(); } // Focus input after initialization this.$nextTick(() => { this.focusChatInput(); }); }, loadHistoricalMessages() { // Veilige toegang tot messages met fallback const chatConfig = window.chatConfig || {}; const historicalMessages = chatConfig.messages || []; if (historicalMessages.length > 0) { this.allMessages = historicalMessages.map(msg => { // Zorg voor een correct geformatteerde bericht-object return { id: this.messageIdCounter++, content: typeof msg === 'string' ? msg : msg.content || '', sender: msg.sender || 'ai', type: msg.type || 'text', timestamp: msg.timestamp || new Date().toISOString(), formData: msg.formData || null, status: msg.status || 'delivered' }; }); console.log(`Loaded ${this.allMessages.length} historical messages`); } }, addWelcomeMessage() { this.addMessage( 'Hallo! Ik ben je AI assistant. Vraag gerust om een formulier zoals "contactformulier" of "bestelformulier"!', 'ai', 'text' ); }, setupEventListeners() { // Window resize listener window.addEventListener('resize', this.handleResize); // Keyboard shortcuts document.addEventListener('keydown', this.handleGlobalKeydown); }, cleanup() { window.removeEventListener('resize', this.handleResize); document.removeEventListener('keydown', this.handleGlobalKeydown); }, // Message management addMessage(content, sender, type = 'text', formData = null) { const message = { id: this.messageIdCounter++, content, sender, type, formData, timestamp: new Date().toISOString(), status: sender === 'user' ? 'sent' : 'delivered' }; this.allMessages.push(message); // Initialize form values if it's a form if (type === 'form' && formData) { this.$set(this.formValues, message.id, {}); formData.fields.forEach(field => { this.$set(this.formValues[message.id], field.name, field.defaultValue || ''); }); } // Update search results if searching if (this.isSearching) { this.performSearch(); } return message; }, updateCurrentMessage(value) { this.currentMessage = value; }, // Message sending async sendMessage() { const text = this.currentMessage.trim(); if (!text || this.isLoading) return; console.log('Sending message:', text); // Add user message const userMessage = this.addMessage(text, 'user', 'text'); this.currentMessage = ''; // Show typing and loading state this.isTyping = true; this.isLoading = true; try { const response = await this.callAPI('/api/send_message', { message: text, conversation_id: this.conversationId, user_id: this.userId }); // Hide typing indicator this.isTyping = false; // Mark user message as delivered userMessage.status = 'delivered'; // Add AI response if (response.type === 'form') { this.addMessage('', 'ai', 'form', response.formData); } else { // Voeg het bericht toe met task_id voor tracking - initieel leeg const aiMessage = this.addMessage( '', 'ai', 'text' ); // Voeg task_id toe als beschikbaar if (response.task_id) { console.log('Monitoring Task ID: ', response.task_id); aiMessage.taskId = response.task_id; } } } catch (error) { console.error('Error sending message:', error); this.isTyping = false; // Mark user message as failed userMessage.status = 'failed'; this.addMessage( 'Sorry, er ging iets mis bij het verzenden van je bericht. Probeer het opnieuw.', 'ai', 'error' ); } finally { this.isLoading = false; } }, // Form handling async submitForm(formData, messageId) { this.isSubmittingForm = true; console.log('Submitting form:', formData.title, this.formValues[messageId]); try { const response = await this.callAPI('/api/submit_form', { formData: this.formValues[messageId], formType: formData.title, conversation_id: this.conversationId, user_id: this.userId }); if (response.success) { this.addMessage( `✅ ${response.message || 'Formulier succesvol verzonden!'}`, 'ai', 'text' ); // Remove the form message this.removeMessage(messageId); } else { this.addMessage( `❌ Er ging iets mis: ${response.error || 'Onbekende fout'}`, 'ai', 'text' ); } } catch (error) { console.error('Error submitting form:', error); this.addMessage( 'Sorry, er ging iets mis bij het verzenden van het formulier. Probeer het opnieuw.', 'ai', 'text' ); } finally { this.isSubmittingForm = false; } }, // Message actions retryMessage(messageId) { const message = this.allMessages.find(m => m.id === messageId); if (message && message.status === 'failed') { // Retry sending the message this.currentMessage = message.content; this.removeMessage(messageId); this.sendMessage(); } }, removeMessage(messageId) { const index = this.allMessages.findIndex(m => m.id === messageId); if (index !== -1) { this.allMessages.splice(index, 1); // Verwijder ook eventuele formuliergegevens if (this.formValues[messageId]) { delete this.formValues[messageId]; } } }, // File handling async handleFileUpload(file) { console.log('Uploading file:', file.name); // Add file message const fileMessage = this.addMessage('', 'user', 'file', { fileName: file.name, fileSize: this.formatFileSize(file.size), fileType: file.type }); try { // TODO: Implement actual file upload // const response = await this.uploadFile(file); // fileMessage.fileUrl = response.url; // Simulate file upload setTimeout(() => { fileMessage.fileUrl = URL.createObjectURL(file); fileMessage.status = 'delivered'; }, 1000); } catch (error) { console.error('Error uploading file:', error); fileMessage.status = 'failed'; } }, async handleVoiceRecord(audioBlob) { console.log('Processing voice recording'); // Add voice message const voiceMessage = this.addMessage('', 'user', 'voice', { audioBlob, duration: '00:05' // TODO: Calculate actual duration }); // TODO: Send to speech-to-text service // const transcription = await this.transcribeAudio(audioBlob); // this.currentMessage = transcription; // this.sendMessage(); }, // Search functionality performSearch() { if (!this.messageSearch.trim()) { this.isSearching = false; this.filteredMessages = []; return; } this.isSearching = true; const query = this.messageSearch.toLowerCase(); this.filteredMessages = this.allMessages.filter(message => message.content && message.content.toLowerCase().includes(query) ); }, clearSearch() { this.messageSearch = ''; this.isSearching = false; this.filteredMessages = []; }, // Event handlers handleResize() { this.isMobile = window.innerWidth <= 768; this.showSidebar = window.innerWidth > 768; }, handleGlobalKeydown(event) { // Ctrl/Cmd + K for search if ((event.ctrlKey || event.metaKey) && event.key === 'k') { event.preventDefault(); this.focusSearch(); } // Escape to clear search if (event.key === 'Escape' && this.isSearching) { this.clearSearch(); } }, // Utility methods async callAPI(endpoint, data) { // Gebruik de API prefix uit de lokale variabele const fullEndpoint = this.apiPrefix + '/chat' + endpoint; console.log('Calling API with prefix:', { prefix: this.apiPrefix, endpoint: endpoint, fullEndpoint: fullEndpoint }); const response = await fetch(fullEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }, formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }, focusChatInput() { this.$refs.chatInput?.focusInput(); }, focusSearch() { this.$refs.searchInput?.focus(); }, handleSpecialistError(errorData) { console.error('Specialist error:', errorData); // Als we willen kunnen we hier nog extra logica toevoegen, zoals statistieken bijhouden of centraal loggen }, }, template: `
` }; // Initialize app when DOM is ready document.addEventListener('DOMContentLoaded', () => { console.log('Initializing Chat Application'); // Get access to the existing Vue app instance if (window.__vueApp) { // Register ALL components globally window.__vueApp.component('TypingIndicator', TypingIndicator); window.__vueApp.component('FormField', FormField); window.__vueApp.component('DynamicForm', DynamicForm); window.__vueApp.component('ChatMessage', ChatMessage); window.__vueApp.component('MessageHistory', MessageHistory); window.__vueApp.component('ChatInput', ChatInput); window.__vueApp.component('ProgressTracker', ProgressTracker); console.log('All chat components registered with existing Vue instance'); // Register the ChatApp component window.__vueApp.component('ChatApp', ChatApp); console.log('ChatApp component registered with existing Vue instance'); // Mount the Vue app window.__vueApp.mount('#app'); console.log('Vue app mounted with chat components'); } else { console.error('No existing Vue instance found on window.__vueApp'); } });