From f1c60f9574d01e678ce6c2661c7062718903ef6b Mon Sep 17 00:00:00 2001 From: Josako Date: Fri, 13 Jun 2025 14:19:05 +0200 Subject: [PATCH] tussentijdse status voor significante wijzigingen. Bezig aan creatie Dynamic Form in de chat client. --- common/utils/cache/config_cache.py | 14 +- config/config.py | 84 ++++++- .../globals/PERSONAL_CONTACT_FORM/1.0.0.yaml | 40 ++++ config/type_defs/specialist_form_types.py | 7 + .../static/css/chat-components.css | 16 -- eveai_chat_client/static/css/form-message.css | 75 +++++++ eveai_chat_client/static/css/form.css | 175 +++++++++++++++ eveai_chat_client/static/js/chat-app.js | 170 +++++++++++++- .../static/js/components/ChatInput.js | 153 +++++++++++-- .../static/js/components/ChatMessage.js | 35 +-- .../static/js/components/DynamicForm.js | 191 +++++++++++----- .../static/js/components/FormField.js | 211 ++++++++---------- .../static/js/components/FormMessage.js | 59 +++++ .../static/js/components/MessageHistory.js | 2 + .../static/js/components/ProgressTracker.js | 3 +- .../specialists/specialist_typing.py | 22 +- .../TRAICIE_SELECTION_SPECIALIST/1_1.py | 10 +- 17 files changed, 1012 insertions(+), 255 deletions(-) create mode 100644 config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml create mode 100644 config/type_defs/specialist_form_types.py create mode 100644 eveai_chat_client/static/css/form-message.css create mode 100644 eveai_chat_client/static/css/form.css create mode 100644 eveai_chat_client/static/js/components/FormMessage.js diff --git a/common/utils/cache/config_cache.py b/common/utils/cache/config_cache.py index dd7c267..c72c19d 100644 --- a/common/utils/cache/config_cache.py +++ b/common/utils/cache/config_cache.py @@ -7,7 +7,7 @@ from flask import current_app from common.utils.cache.base import CacheHandler, CacheKey from config.type_defs import agent_types, task_types, tool_types, specialist_types, retriever_types, prompt_types, \ - catalog_types, partner_service_types, processor_types, customisation_types + catalog_types, partner_service_types, processor_types, customisation_types, specialist_form_types def is_major_minor(version: str) -> bool: @@ -478,6 +478,14 @@ CustomisationConfigCacheHandler, CustomisationConfigVersionTreeCacheHandler, Cus ) ) +SpecialistFormConfigCacheHandler, SpecialistFormConfigVersionTreeCacheHandler, SpecialistFormConfigTypesCacheHandler = ( + create_config_cache_handlers( + config_type='specialist_forms', + config_dir='config/specialist_forms', + types_module=specialist_form_types.SPECIALIST_FORM_TYPES + ) +) + def register_config_cache_handlers(cache_manager) -> None: cache_manager.register_handler(AgentConfigCacheHandler, 'eveai_config') @@ -513,6 +521,9 @@ def register_config_cache_handlers(cache_manager) -> None: cache_manager.register_handler(CustomisationConfigCacheHandler, 'eveai_config') cache_manager.register_handler(CustomisationConfigTypesCacheHandler, 'eveai_config') cache_manager.register_handler(CustomisationConfigVersionTreeCacheHandler, 'eveai_config') + cache_manager.register_handler(SpecialistFormConfigCacheHandler, 'eveai_config') + cache_manager.register_handler(SpecialistFormConfigTypesCacheHandler, 'eveai_config') + cache_manager.register_handler(SpecialistFormConfigVersionTreeCacheHandler, 'eveai_config') cache_manager.agents_config_cache.set_version_tree_cache(cache_manager.agents_version_tree_cache) cache_manager.tasks_config_cache.set_version_tree_cache(cache_manager.tasks_version_tree_cache) @@ -524,3 +535,4 @@ def register_config_cache_handlers(cache_manager) -> None: cache_manager.processors_config_cache.set_version_tree_cache(cache_manager.processors_version_tree_cache) cache_manager.partner_services_config_cache.set_version_tree_cache(cache_manager.partner_services_version_tree_cache) cache_manager.customisations_config_cache.set_version_tree_cache(cache_manager.customisations_version_tree_cache) + cache_manager.specialist_forms_config_cache.set_version_tree_cache(cache_manager.specialist_forms_version_tree_cache) diff --git a/config/config.py b/config/config.py index cde13f1..6c677d2 100644 --- a/config/config.py +++ b/config/config.py @@ -67,7 +67,89 @@ class Config(object): MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # supported languages - SUPPORTED_LANGUAGES = ['en', 'fr', 'nl', 'de', 'es'] + SUPPORTED_LANGUAGES = ['en', 'fr', 'nl', 'de', 'es', 'it', 'pt', 'ru', 'zh', 'ja', 'ko', 'ar', 'hi'] + SUPPORTED_LANGUAGE_DETAILS = { + "English": { + "iso 639-1": "en", + "iso 639-2": "eng", + "iso 639-3": "eng", + "flag": "๐Ÿ‡ฌ๐Ÿ‡ง" + }, + "French": { + "iso 639-1": "fr", + "iso 639-2": "fre", # of 'fra' + "iso 639-3": "fra", + "flag": "๐Ÿ‡ซ๐Ÿ‡ท" + }, + "German": { + "iso 639-1": "de", + "iso 639-2": "ger", # of 'deu' + "iso 639-3": "deu", + "flag": "๐Ÿ‡ฉ๐Ÿ‡ช" + }, + "Spanish": { + "iso 639-1": "es", + "iso 639-2": "spa", + "iso 639-3": "spa", + "flag": "๐Ÿ‡ช๐Ÿ‡ธ" + }, + "Italian": { + "iso 639-1": "it", + "iso 639-2": "ita", + "iso 639-3": "ita", + "flag": "๐Ÿ‡ฎ๐Ÿ‡น" + }, + "Portuguese": { + "iso 639-1": "pt", + "iso 639-2": "por", + "iso 639-3": "por", + "flag": "๐Ÿ‡ต๐Ÿ‡น" + }, + "Dutch": { + "iso 639-1": "nl", + "iso 639-2": "dut", # of 'nld' + "iso 639-3": "nld", + "flag": "๐Ÿ‡ณ๐Ÿ‡ฑ" + }, + "Russian": { + "iso 639-1": "ru", + "iso 639-2": "rus", + "iso 639-3": "rus", + "flag": "๐Ÿ‡ท๐Ÿ‡บ" + }, + "Chinese": { + "iso 639-1": "zh", + "iso 639-2": "chi", # of 'zho' + "iso 639-3": "zho", + "flag": "๐Ÿ‡จ๐Ÿ‡ณ" + }, + "Japanese": { + "iso 639-1": "ja", + "iso 639-2": "jpn", + "iso 639-3": "jpn", + "flag": "๐Ÿ‡ฏ๐Ÿ‡ต" + }, + "Korean": { + "iso 639-1": "ko", + "iso 639-2": "kor", + "iso 639-3": "kor", + "flag": "๐Ÿ‡ฐ๐Ÿ‡ท" + }, + "Arabic": { + "iso 639-1": "ar", + "iso 639-2": "ara", + "iso 639-3": "ara", + "flag": "๐Ÿ‡ธ๐Ÿ‡ฆ" + }, + "Hindi": { + "iso 639-1": "hi", + "iso 639-2": "hin", + "iso 639-3": "hin", + "flag": "๐Ÿ‡ฎ๐Ÿ‡ณ" + }, + } + + SUPPORTED_LANGUAGES_Full = list(SUPPORTED_LANGUAGE_DETAILS.keys()) # supported currencies SUPPORTED_CURRENCIES = ['โ‚ฌ', '$'] diff --git a/config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml b/config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml new file mode 100644 index 0000000..7e5a02d --- /dev/null +++ b/config/specialist_forms/globals/PERSONAL_CONTACT_FORM/1.0.0.yaml @@ -0,0 +1,40 @@ +version: "1.0.0" +name: "Personal Contact Form" +icon: "call" +fields: + name: + name: "Name" + description: "Your name" + type: "str" + required: true + email: + name: "Email" + type: "str" + description: "Your Name" + required: true + phone: + name: "Phone Number" + type: "str" + description: "Your Phone Number" + required: true + Address: + name: "Address" + type: "text" + description: "Your Address" + required: false + status: + name: "Marital Status" + type: "enum" + description: "Your Marital Status" + required: false + default: "single" + allowed_values: + - "single" + - "married" + - "divorced" + can_contact: + name: "Allow Contact" + type: "boolean" + description: "Allow us to contact you?" + required: true + default: false \ No newline at end of file diff --git a/config/type_defs/specialist_form_types.py b/config/type_defs/specialist_form_types.py new file mode 100644 index 0000000..57079e4 --- /dev/null +++ b/config/type_defs/specialist_form_types.py @@ -0,0 +1,7 @@ +# Specialist Form Types +SPECIALIST_FORM_TYPES = { + "PERSONAL_CONTACT_FORM": { + "name": "Contact Form", + "description": "A form for entering your personal contact details", + }, +} \ No newline at end of file diff --git a/eveai_chat_client/static/css/chat-components.css b/eveai_chat_client/static/css/chat-components.css index 7f0df41..edb070c 100644 --- a/eveai_chat_client/static/css/chat-components.css +++ b/eveai_chat_client/static/css/chat-components.css @@ -408,22 +408,6 @@ } -/* Edit mode styling */ -.edit-mode .edit-textarea { - width: 100%; - padding: 8px; - border: 1px solid #ddd; - border-radius: 4px; - resize: vertical; - min-height: 60px; - font-family: inherit; - margin-bottom: 8px; -} - -.edit-actions { - display: flex; - gap: 8px; -} .btn-small { padding: 4px 12px; diff --git a/eveai_chat_client/static/css/form-message.css b/eveai_chat_client/static/css/form-message.css new file mode 100644 index 0000000..6b3b636 --- /dev/null +++ b/eveai_chat_client/static/css/form-message.css @@ -0,0 +1,75 @@ +/* Stijlen voor het FormMessage component in chatberichten */ +.form-message { + background-color: #f5f8ff; + border: 1px solid #e1e8f5; + border-radius: 8px; + padding: 12px; + margin-bottom: 10px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.form-message-header { + display: flex; + align-items: center; + margin-bottom: 10px; + padding-bottom: 8px; + border-bottom: 1px solid #e1e8f5; +} + +.form-message-icon { + font-size: 16px; + margin-right: 8px; + color: #4a6fa5; +} + +.form-message-title { + font-weight: 600; + color: #3a5a80; + font-size: 0.95rem; +} + +.form-message-fields { + display: flex; + flex-direction: column; + gap: 6px; +} + +.form-message-field { + display: flex; + flex-wrap: wrap; +} + +.field-message-label { + flex: 0 0 120px; + font-weight: 500; + color: #4a6fa5; + font-size: 0.85rem; + padding-right: 8px; +} + +.field-message-value { + flex: 1; + min-width: 0; + font-size: 0.9rem; + color: #333; + word-break: break-word; +} + +.field-message-value.text-value { + white-space: pre-wrap; +} + +@media (max-width: 576px) { + .form-message-field { + flex-direction: column; + } + + .field-message-label { + flex: 0 0 100%; + margin-bottom: 2px; + } + + .field-message-value { + padding-left: 8px; + } +} diff --git a/eveai_chat_client/static/css/form.css b/eveai_chat_client/static/css/form.css new file mode 100644 index 0000000..3992aa2 --- /dev/null +++ b/eveai_chat_client/static/css/form.css @@ -0,0 +1,175 @@ +/* Dynamisch formulier stijlen */ +.dynamic-form-container { + margin-bottom: 15px; + border: 1px solid #e0e0e0; + border-radius: 8px; + overflow: hidden; + background-color: #f9f9f9; +} + +.dynamic-form { + padding: 15px; +} + +.form-header { + display: flex; + align-items: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 1px solid #e0e0e0; +} + +.form-icon { + margin-right: 10px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + color: #555; +} + +.form-title { + font-size: 1.2rem; + font-weight: 600; + color: #333; +} + +.form-fields { + display: grid; + grid-template-columns: 1fr; + gap: 15px; + margin-bottom: 20px; +} + +@media (min-width: 768px) { + .form-fields { + grid-template-columns: repeat(2, 1fr); + } +} + +.form-field { + margin-bottom: 5px; +} + +.form-field label { + display: block; + margin-bottom: 6px; + font-weight: 500; + font-size: 0.9rem; + color: #555; +} + +.form-field input, +.form-field select, +.form-field textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.9rem; + background-color: #fff; +} + +.form-field input:focus, +.form-field select:focus, +.form-field textarea:focus { + outline: none; + border-color: #4a90e2; + box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2); +} + +.form-field textarea { + min-height: 80px; + resize: vertical; +} + +.checkbox-container { + display: flex; + align-items: center; +} + +.checkbox-label { + display: flex; + align-items: center; + cursor: pointer; +} + +.checkbox-label input[type="checkbox"] { + width: auto; + margin-right: 8px; +} + +.checkbox-text { + font-size: 0.9rem; + color: #555; +} + +.field-description { + display: block; + margin-top: 5px; + font-size: 0.8rem; + color: #777; + line-height: 1.4; +} + +.form-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 10px; +} + +.form-toggle-btn { + background: none; + border: none; + cursor: pointer; + padding: 5px; + color: #555; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; +} + +.form-toggle-btn:hover { + background-color: #f0f0f0; +} + +.form-toggle-btn.active { + color: #4a90e2; + background-color: rgba(74, 144, 226, 0.1); +} + +.required { + color: #e53935; + margin-left: 2px; +} + +/* Read-only form styling */ +.form-readonly { + padding: 10px 0; +} + +.form-field-readonly { + display: flex; + margin-bottom: 8px; + padding-bottom: 8px; + border-bottom: 1px solid #eee; +} + +.field-label { + flex: 0 0 30%; + font-weight: 500; + color: #555; + padding-right: 10px; +} + +.field-value { + flex: 1; + word-break: break-word; +} + +.text-value { + white-space: pre-wrap; +} diff --git a/eveai_chat_client/static/js/chat-app.js b/eveai_chat_client/static/js/chat-app.js index cbbf87d..4e547b4 100644 --- a/eveai_chat_client/static/js/chat-app.js +++ b/eveai_chat_client/static/js/chat-app.js @@ -36,6 +36,7 @@ export const ChatApp = { isSubmittingForm: false, messageIdCounter: 1, formValues: {}, + currentInputFormData: null, // API prefix voor endpoints apiPrefix: chatConfig.apiPrefix || '', @@ -298,6 +299,53 @@ export const ChatApp = { } }, + async submitFormFromInput(formValues) { + this.isSubmittingForm = true; + + if (!this.currentInputFormData) { + console.error('No form data available'); + return; + } + + console.log('Submitting form from input:', this.currentInputFormData.title, formValues); + + try { + const response = await this.callAPI('/api/submit_form', { + formData: formValues, + formType: this.currentInputFormData.title, + conversation_id: this.conversationId, + user_id: this.userId + }); + + if (response.success) { + this.addMessage( + `โœ… ${response.message || 'Formulier succesvol verzonden!'}`, + 'ai', + 'text' + ); + + // Wis het huidige formulier + this.currentInputFormData = null; + } 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) { @@ -387,6 +435,56 @@ export const ChatApp = { this.filteredMessages = []; }, + // Event handlers voor specialist events + handleSpecialistComplete(eventData) { + console.log('ChatApp received specialist-complete:', eventData); + + // Als er een form_request is, voeg deze toe als nieuw bericht + if (eventData.form_request) { + console.log('Adding form request as new message:', eventData.form_request); + + // Converteer de form_request naar het verwachte formaat + const formData = this.convertFormRequest(eventData.form_request); + + // Voeg het formulier toe als een nieuw AI bericht + this.addMessage('', 'ai', 'form', formData); + } + }, + + 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); + + // Converteer de fields van object naar array formaat + const fieldsArray = Object.entries(formRequest.fields || {}).map(([fieldId, fieldDef]) => ({ + id: fieldId, + name: fieldDef.name, + type: fieldDef.type, + description: fieldDef.description, + required: fieldDef.required || false, + defaultValue: fieldDef.default || '', + allowedValues: fieldDef.allowed_values || null + })); + + return { + title: formRequest.name, + icon: formRequest.icon || 'form', + version: formRequest.version || '1.0', + fields: fieldsArray + }; + }, + // Event handlers handleResize() { this.isMobile = window.innerWidth <= 768; @@ -450,11 +548,54 @@ export const ChatApp = { 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 - }, + handleSpecialistComplete(eventData) { + console.log('ChatApp received specialist-complete:', eventData); + + // Als er een form_request is, stuur deze naar de ChatInput component + if (eventData.form_request) { + console.log('Providing form request to ChatInput:', eventData.form_request); + // Converteer de form_request naar het verwachte formaat + const formData = this.convertFormRequest(eventData.form_request); + + // Update de currentInputFormData voor ChatInput + this.currentInputFormData = formData; + } + }, + + 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); + + // Converteer de fields van object naar array formaat + const fieldsArray = Object.entries(formRequest.fields || {}).map(([fieldId, fieldDef]) => ({ + id: fieldId, + name: fieldDef.name, + type: fieldDef.type, + description: fieldDef.description, + required: fieldDef.required || false, + defaultValue: fieldDef.default || '', + allowedValues: fieldDef.allowed_values || null + })); + + return { + title: formRequest.name, + icon: formRequest.icon || 'form', + version: formRequest.version || '1.0', + fields: fieldsArray + }; + }, }, template: ` @@ -469,6 +610,7 @@ export const ChatApp = { :auto-scroll="true" @submit-form="submitForm" @specialist-error="handleSpecialistError" + @specialist-complete="handleSpecialistComplete" ref="messageHistory" class="chat-messages-area" > @@ -480,10 +622,12 @@ export const ChatApp = { :max-length="2000" :allow-file-upload="true" :allow-voice-message="false" + :form-data="currentInputFormData" @send-message="sendMessage" @update-message="updateCurrentMessage" @upload-file="handleFileUpload" @record-voice="handleVoiceRecord" + @submit-form="submitFormFromInput" ref="chatInput" class="chat-input-area" > @@ -493,12 +637,21 @@ export const ChatApp = { }; -// Initialize app when DOM is ready -document.addEventListener('DOMContentLoaded', () => { +// Zorg ervoor dat alle componenten correct geรฏnitialiseerd zijn voordat ze worden gebruikt +const initializeApp = () => { console.log('Initializing Chat Application'); // Get access to the existing Vue app instance if (window.__vueApp) { + // Zorg ervoor dat alle componenten globaal beschikbaar zijn via window + window.TypingIndicator = TypingIndicator; + window.FormField = FormField; + window.DynamicForm = DynamicForm; + window.ChatMessage = ChatMessage; + window.MessageHistory = MessageHistory; + window.ChatInput = ChatInput; + window.ProgressTracker = ProgressTracker; + // Register ALL components globally window.__vueApp.component('TypingIndicator', TypingIndicator); window.__vueApp.component('FormField', FormField); @@ -520,4 +673,7 @@ document.addEventListener('DOMContentLoaded', () => { } else { console.error('No existing Vue instance found on window.__vueApp'); } -}); \ No newline at end of file +}; + +// Initialize app when DOM is ready +document.addEventListener('DOMContentLoaded', initializeApp); \ No newline at end of file diff --git a/eveai_chat_client/static/js/components/ChatInput.js b/eveai_chat_client/static/js/components/ChatInput.js index 139baaa..446feb1 100644 --- a/eveai_chat_client/static/js/components/ChatInput.js +++ b/eveai_chat_client/static/js/components/ChatInput.js @@ -2,6 +2,9 @@ export const ChatInput = { name: 'ChatInput', + components: { + 'dynamic-form': window.__vueApp ? DynamicForm : null + }, props: { currentMessage: { type: String, @@ -19,11 +22,38 @@ export const ChatInput = { type: Number, default: 2000 }, + formData: { + type: Object, + default: null + }, + }, + emits: ['send-message', 'update-message', 'submit-form'], + watch: { + formData: { + handler(newFormData) { + console.log('ChatInput received formData:', newFormData); + if (newFormData) { + this.formValues = {}; // Reset formulierwaarden + this.showForm = true; + } else { + this.showForm = false; + } + }, + immediate: true + }, + currentMessage(newVal) { + this.localMessage = newVal; + }, + localMessage(newVal) { + this.$emit('update-message', newVal); + this.autoResize(); + } }, - emits: ['send-message', 'update-message'], data() { return { localMessage: this.currentMessage, + formValues: {}, + showForm: false }; }, computed: { @@ -35,19 +65,21 @@ export const ChatInput = { return this.characterCount > this.maxLength; }, - canSend() { - return this.localMessage.trim() && - !this.isLoading && - !this.isOverLimit; - } - }, - watch: { - currentMessage(newVal) { - this.localMessage = newVal; + hasFormData() { + return this.formData && this.formData.fields && + ((Array.isArray(this.formData.fields) && this.formData.fields.length > 0) || + (typeof this.formData.fields === 'object' && Object.keys(this.formData.fields).length > 0)); }, - localMessage(newVal) { - this.$emit('update-message', newVal); - this.autoResize(); + + canSend() { + const hasValidForm = this.showForm && this.formData && this.validateForm(); + const hasValidMessage = this.localMessage.trim() && !this.isOverLimit; + + return (!this.isLoading) && (hasValidForm || hasValidMessage); + }, + + sendButtonText() { + return this.showForm ? 'Verstuur formulier' : 'Verstuur bericht'; } }, mounted() { @@ -64,11 +96,51 @@ export const ChatInput = { }, sendMessage() { - if (this.canSend) { + if (!this.canSend) return; + + if (this.showForm && this.formData) { + // Valideer het formulier + if (this.validateForm()) { + // Verstuur het formulier + this.$emit('submit-form', this.formValues); + this.formValues = {}; + // Reset het formulier na verzenden + this.showForm = false; + } + } else if (this.localMessage.trim()) { + // Verstuur normaal bericht this.$emit('send-message'); } }, + validateForm() { + if (!this.formData || !this.formData.fields) return false; + + // Controleer of alle verplichte velden zijn ingevuld + let missingFields = []; + + if (Array.isArray(this.formData.fields)) { + missingFields = this.formData.fields.filter(field => { + if (!field.required) return false; + const fieldId = field.id || field.name; + const value = this.formValues[fieldId]; + return value === undefined || value === null || (typeof value === 'string' && !value.trim()); + }); + } else { + // Voor object-gebaseerde velden + Object.entries(this.formData.fields).forEach(([fieldId, field]) => { + if (field.required) { + const value = this.formValues[fieldId]; + if (value === undefined || value === null || (typeof value === 'string' && !value.trim())) { + missingFields.push(field); + } + } + }); + } + + return missingFields.length === 0; + }, + autoResize() { this.$nextTick(() => { const textarea = this.$refs.messageInput; @@ -86,10 +158,44 @@ export const ChatInput = { clearInput() { this.localMessage = ''; this.focusInput(); + }, + + toggleForm() { + this.showForm = !this.showForm; + if (!this.showForm) { + this.focusInput(); + } + }, + + submitForm() { + if (this.canSubmitForm) { + this.$emit('submit-form', { ...this.formValues }); + this.showForm = false; + this.focusInput(); + } + }, + + cancelForm() { + this.showForm = false; + this.focusInput(); + }, + + updateFormValues(newValues) { + this.formValues = { ...newValues }; } }, template: `
+
+ +
+
@@ -104,21 +210,34 @@ export const ChatInput = { class="message-input" :class="{ 'over-limit': isOverLimit }" > - +
{{ characterCount }}/{{ maxLength }}
- +
+ + + - -
-
- -
+
{ - return formData.title && formData.fields && Array.isArray(formData.fields); + return formData && formData.title && formData.fields && + (Array.isArray(formData.fields) || typeof formData.fields === 'object'); } }, formValues: { type: Object, - required: true + default: () => ({}) }, isSubmitting: { type: Boolean, default: false + }, + readOnly: { + type: Boolean, + default: false + }, + hideActions: { + type: Boolean, + default: false + } + }, + emits: ['submit', 'cancel', 'update:formValues'], + data() { + return { + localFormValues: { ...this.formValues } + }; + }, + watch: { + formValues: { + handler(newValues) { + this.localFormValues = { ...newValues }; + }, + deep: true + }, + localFormValues: { + handler(newValues) { + this.$emit('update:formValues', newValues); + }, + deep: true } }, - emits: ['submit', 'cancel'], methods: { handleSubmit() { // Basic validation - const requiredFields = this.formData.fields.filter(field => field.required); - const missingFields = requiredFields.filter(field => { - const value = this.formValues[field.name]; - return !value || (typeof value === 'string' && !value.trim()); - }); + const missingFields = []; + + if (Array.isArray(this.formData.fields)) { + // Valideer array-gebaseerde velden + this.formData.fields.forEach(field => { + const fieldId = field.id || field.name; + if (field.required) { + const value = this.localFormValues[fieldId]; + if (value === undefined || value === null || + (typeof value === 'string' && !value.trim()) || + (Array.isArray(value) && value.length === 0)) { + missingFields.push(field.name); + } + } + }); + } else { + // Valideer object-gebaseerde velden + Object.entries(this.formData.fields).forEach(([fieldId, field]) => { + if (field.required) { + const value = this.localFormValues[fieldId]; + if (value === undefined || value === null || + (typeof value === 'string' && !value.trim()) || + (Array.isArray(value) && value.length === 0)) { + missingFields.push(field.name); + } + } + }); + }; if (missingFields.length > 0) { - const fieldNames = missingFields.map(f => f.label).join(', '); + const fieldNames = missingFields.join(', '); alert(`De volgende velden zijn verplicht: ${fieldNames}`); return; } - this.$emit('submit'); + this.$emit('submit', this.localFormValues); }, handleCancel() { this.$emit('cancel'); }, - updateFieldValue(fieldName, value) { - // Emit an update for reactive binding - this.$emit('update-field', fieldName, value); + updateFieldValue(fieldId, value) { + this.localFormValues[fieldId] = value; } }, template: ` -
-
{{ formData.title }}
- -
- {{ formData.description }} +
+
+
+ {{ formData.icon }} +
+
{{ formData.title }}
- -
- - -
+ +
+ + + + +
+ + +
+ + +
+ +
- + - - - -
- - -
-
-
-
- Stap {{ formData.currentStep }} van {{ formData.steps }}
diff --git a/eveai_chat_client/static/js/components/FormField.js b/eveai_chat_client/static/js/components/FormField.js index a1a06dd..8a74e05 100644 --- a/eveai_chat_client/static/js/components/FormField.js +++ b/eveai_chat_client/static/js/components/FormField.js @@ -5,22 +5,53 @@ export const FormField = { type: Object, required: true, validator: (field) => { - return field.name && field.type && field.label; + return field.name && field.type; } }, + fieldId: { + type: String, + required: true + }, modelValue: { - default: '' + default: null } }, emits: ['update:modelValue'], computed: { value: { get() { + // Gebruik default waarde als modelValue undefined is + if (this.modelValue === undefined || this.modelValue === null) { + if (this.field.type === 'boolean') { + return this.field.default === true; + } + return this.field.default !== undefined ? this.field.default : ''; + } return this.modelValue; }, set(value) { this.$emit('update:modelValue', value); } + }, + fieldType() { + // Map Python types naar HTML input types + const typeMap = { + 'str': 'text', + 'string': 'text', + 'int': 'number', + 'integer': 'number', + 'float': 'number', + 'text': 'textarea', + 'enum': 'select', + 'boolean': 'checkbox' + }; + return typeMap[this.field.type] || this.field.type; + }, + stepValue() { + return this.field.type === 'float' ? 'any' : 1; + }, + description() { + return this.field.description || ''; } }, methods: { @@ -33,147 +64,83 @@ export const FormField = { }, template: `
-