// 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 { ProgressTracker } from '/static/assets/js/components/ProgressTracker.js'; // Maak componenten globaal beschikbaar voordat andere componenten worden geladen window.DynamicForm = DynamicForm; window.FormField = FormField; window.TypingIndicator = TypingIndicator; window.ChatMessage = ChatMessage; window.MessageHistory = MessageHistory; window.ProgressTracker = ProgressTracker; // Nu kunnen we ChatInput importeren nadat de benodigde componenten globaal beschikbaar zijn import { ChatInput } from '/static/assets/js/components/ChatInput.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: {}, currentInputFormData: null, // 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, formValues = null) { const message = { id: this.messageIdCounter++, content, sender, type, formData, formValues, timestamp: new Date().toISOString(), status: sender === 'user' ? 'sent' : 'delivered' }; this.allMessages.push(message); // Initialize form values if it's a form and no values were provided if (type === 'form' && formData && !formValues) { // Vue 3 compatibele manier om reactieve objecten bij te werken this.formValues[message.id] = {}; formData.fields.forEach(field => { const fieldName = field.name || field.id; if (fieldName) { this.formValues[message.id][fieldName] = field.defaultValue || ''; } }); } // Update search results if searching if (this.isSearching) { this.performSearch(); } return message; }, // Helper functie om formulierdata toe te voegen aan bestaande berichten attachFormDataToMessage(messageId, formData, formValues) { const message = this.allMessages.find(m => m.id === messageId); if (message) { message.formData = formData; message.formValues = formValues; } }, updateCurrentMessage(value) { this.currentMessage = value; }, // Message sending (alleen voor gewone tekstberichten, geen formulieren) async sendMessage() { const text = this.currentMessage.trim(); // Controleer of we kunnen verzenden if (!text || this.isLoading) return; console.log('Sending text message:', text); // Add user message const userMessage = this.addMessage(text, 'user', 'text'); // Wis input this.currentMessage = ''; // Show typing and loading state this.isTyping = true; this.isLoading = true; try { // Verzamel gegevens voor de API call const apiData = { message: text, conversation_id: this.conversationId, user_id: this.userId }; const response = await this.callAPI('/api/send_message', apiData); // 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; } }, async submitFormFromInput(formValues) { this.isSubmittingForm = true; if (!this.currentInputFormData) { console.error('No form data available'); return; } console.log('Form values received:', formValues); console.log('Current input form data:', this.currentInputFormData); try { // Maak een user message met formuliergegevens én eventuele tekst const userMessage = this.addMessage( this.currentMessage.trim(), // Voeg tekst toe als die er is 'user', 'text' ); // Voeg formuliergegevens toe aan het bericht userMessage.formData = this.currentInputFormData; userMessage.formValues = formValues; // Reset het tekstbericht this.currentMessage = ''; this.$emit('update-message', ''); // Toon laad-indicator this.isTyping = true; this.isLoading = true; // Verzamel gegevens voor de API call const apiData = { message: userMessage.content, conversation_id: this.conversationId, user_id: this.userId, form_values: formValues // Voeg formuliergegevens toe aan API call }; // Verstuur bericht naar de API const response = await this.callAPI('/api/send_message', apiData); // Verberg de typing indicator this.isTyping = false; // Markeer het gebruikersbericht als afgeleverd userMessage.status = 'delivered'; // Voeg AI response toe met task_id voor tracking const aiMessage = this.addMessage( '', 'ai', 'text' ); if (response.task_id) { console.log('Monitoring Task ID: ', response.task_id); aiMessage.taskId = response.task_id; } // Reset formulier na succesvolle verzending this.currentInputFormData = null; } 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' ); // Wis ook hier het formulier na een fout this.currentInputFormData = null; } finally { this.isSubmittingForm = false; this.isLoading = 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 voor specialist events handleSpecialistComplete(eventData) { console.log('ChatApp received specialist-complete:', eventData); // Als er een form_request is, toon deze in de ChatInput component if (eventData.form_request) { console.log('Setting form request in ChatInput:', eventData.form_request); try { // Converteer de form_request naar het verwachte formaat const formData = this.convertFormRequest(eventData.form_request); // Stel het formulier in als currentInputFormData in plaats van als bericht toe te voegen if (formData && formData.title && formData.fields) { this.currentInputFormData = formData; } else { console.error('Invalid form data after conversion:', formData); } } catch (err) { console.error('Error processing form request:', err); } } }, handleSpecialistError(eventData) { console.log('ChatApp received specialist-error:', eventData); // Voeg foutbericht toe this.addMessage( eventData.message || 'Er is een fout opgetreden bij het verwerken van uw verzoek.', 'ai', 'error' ); }, // Helper methode om form_request te converteren naar het verwachte formaat convertFormRequest(formRequest) { console.log('Converting form request:', formRequest); if (!formRequest) { console.error('Geen geldig formRequest ontvangen'); return null; } // Controleer of fields een object is voordat we converteren let fieldsArray; if (formRequest.fields && typeof formRequest.fields === 'object' && !Array.isArray(formRequest.fields)) { // Converteer de fields van object naar array formaat fieldsArray = Object.entries(formRequest.fields).map(([fieldId, fieldDef]) => ({ id: fieldId, name: fieldDef.name || fieldId, // Gebruik fieldId als fallback type: fieldDef.type || 'text', // Standaard naar text description: fieldDef.description || '', required: fieldDef.required || false, default: fieldDef.default || '', allowedValues: fieldDef.allowed_values || null, context: fieldDef.context || null })); } else if (Array.isArray(formRequest.fields)) { // Als het al een array is, zorg dat alle velden correct zijn fieldsArray = formRequest.fields.map(field => ({ id: field.id || field.name, name: field.name || field.id, type: field.type || 'text', description: field.description || '', required: field.required || false, default: field.default || field.defaultValue || '', allowedValues: field.allowed_values || field.allowedValues || null, context: field.context || null })); } else { // Fallback naar lege array als er geen velden zijn console.warn('Formulier heeft geen geldige velden'); fieldsArray = []; } return { title: formRequest.name || formRequest.title || 'Formulier', description: formRequest.description || '', icon: formRequest.icon || 'form', version: formRequest.version || '1.0', fields: fieldsArray }; }, // 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(); }, }, template: `
` }; // Zorg ervoor dat alle componenten correct geïnitialiseerd zijn voordat ze worden gebruikt const initializeApp = () => { console.log('Initializing Chat Application'); // ChatInput wordt pas op dit punt globaal beschikbaar gemaakt // omdat het afhankelijk is van andere componenten window.ChatInput = ChatInput; // 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'); } }; // Initialize app when DOM is ready document.addEventListener('DOMContentLoaded', initializeApp);