diff --git a/SIDEBAR_MIGRATION_SUMMARY.md b/SIDEBAR_MIGRATION_SUMMARY.md new file mode 100644 index 0000000..e46914c --- /dev/null +++ b/SIDEBAR_MIGRATION_SUMMARY.md @@ -0,0 +1,235 @@ +# SideBar Vue Component Migration - Complete Implementation Summary + +## ๐ŸŽฏ Migration Overview + +Successfully migrated the entire sidebar from static HTML to modern Vue 3 components, following the same architectural pattern as ChatApp.vue. This creates a consistent, maintainable, and future-proof sidebar system. + +## ๐Ÿ“ Files Created + +### Vue Components +1. **SideBarLogo.vue** - Logo display with fallback initials +2. **SideBarMakeName.vue** - Tenant make name and subtitle display +3. **SideBarExplanation.vue** - Translatable explanation content with markdown support +4. **SideBar.vue** - Main orchestrating component + +### Test Files +5. **test_sidebar_components.js** - Comprehensive test suite for sidebar functionality + +## ๐Ÿ”ง Files Modified + +### Templates +- **base.html** - Replaced static sidebar HTML with `#sidebar-container` + +### JavaScript +- **nginx/frontend_src/js/chat-client.js** - Added SideBar import and replaced old functions with `initializeSidebar()` + +## โœ… Implementation Details + +### 1. SideBarLogo.vue +```vue +- Props: logoUrl, makeName +- Features: Image error handling, initials fallback +- Styling: Responsive logo display with placeholder +``` + +### 2. SideBarMakeName.vue +```vue +- Props: makeName, subtitle +- Features: Conditional subtitle display +- Styling: Centered text with proper hierarchy +``` + +### 3. SideBarExplanation.vue +```vue +- Props: originalText, currentLanguage, apiPrefix +- Features: + * Automatic translation on language change + * Markdown rendering support + * Loading states during translation + * Error handling with fallbacks +- Integration: Uses useTranslationClient composable +``` + +### 4. SideBar.vue (Main Component) +```vue +- Props: tenantMake, explanationText, initialLanguage, etc. +- Features: + * Orchestrates all sub-components + * Handles language change events + * Maintains backward compatibility + * Responsive design +- Event Handling: Emits language-changed events globally +``` + +## ๐Ÿ”„ Migration Changes + +### Before (Static HTML) +```html + +``` + +### After (Vue Component) +```html + +``` + +### JavaScript Changes +**Removed Functions:** +- `fillSidebarExplanation()` +- `initializeLanguageSelector()` + +**Added Function:** +- `initializeSidebar()` - Mounts complete SideBar Vue component + +## ๐ŸŽฏ Key Features Achieved + +### โœ… **Modern Vue 3 Architecture** +- Composition API with ` +``` + +#### 2. useIcon() - Simple Icon Loading + +```vue + +``` + +#### 3. useFormIcon() - Form Data Integration + +```vue + +``` + +## ๐Ÿ”„ Migration Guide + +### From IconManagerMixin (Old) + +```vue + + +``` + +### To Vue 3 Composable (New) + +```vue + + +``` + +## ๐Ÿ“‹ Current Usage in Vue Components + +### All Components Now Use Modern Composables โœ… + +1. **ChatInput.vue** - Uses `useIconManager()` composable +2. **ChatMessage.vue** - Uses `useIconManager()` composable +3. **DynamicForm.vue** - Uses `useIconManager()` composable + +### Zero Legacy Code Remaining โœ… + +- โŒ No IconManagerMixin references +- โŒ No window.iconManager calls +- โŒ No legacy iconManager.js file +- โœ… 100% Vue 3 composables + +## โœ… Complete Modernization Achieved + +### 1. Legacy System Eliminated +- **Before**: Hybrid system with window.iconManager + composables +- **After**: Pure Vue 3 composables, zero legacy dependencies + +### 2. Self-Contained Architecture +- **Before**: Composables depended on external iconManager.js +- **After**: Fully self-contained composables with direct icon loading + +### 3. Optimal Performance +- **Before**: Multiple layers (composable โ†’ window.iconManager โ†’ DOM) +- **After**: Direct composable โ†’ DOM, no intermediate layers + +## ๐Ÿš€ Modern Usage Patterns (100% Vue 3) + +### For Form Components +```vue + +``` + +### For Direct Icon Loading +```vue + +``` + +### For Advanced Icon Management +```vue + +``` + +## ๐Ÿ” Verification + +### Build Status: โœ… SUCCESS +- Chat-client bundle: 263.74 kB +- No build errors +- All Vue SFCs compile correctly +- Zero legacy dependencies + +### Modern Architecture: โœ… VERIFIED +- `useIconManager()` composable โœ… Self-contained +- `useIcon()` composable โœ… Simple loading +- `useFormIcon()` composable โœ… Form integration +- Zero window.iconManager references โœ… + +### Component Integration: โœ… 100% MODERNIZED +- All Vue components use modern composables +- No legacy code remaining +- Pure Vue 3 Composition API throughout + +## ๐Ÿ“ˆ Benefits Achieved + +1. **โœ… Pure Vue 3 Architecture** - Zero legacy dependencies +2. **โœ… Self-Contained System** - No external file dependencies +3. **โœ… Optimal Performance** - Direct DOM manipulation, no layers +4. **โœ… Modern Developer Experience** - Composition API patterns +5. **โœ… Maintainable Codebase** - Single responsibility composables +6. **โœ… Future-Proof** - Built on Vue 3 best practices + +## ๐ŸŽ‰ MISSION ACCOMPLISHED! + +The icon management system is now **100% MODERNIZED** with: +- โœ… Zero legacy code +- โœ… Pure Vue 3 composables +- โœ… Self-contained architecture +- โœ… Optimal performance \ No newline at end of file diff --git a/documentation/TRANSLATION_MANAGEMENT_GUIDE.md b/documentation/TRANSLATION_MANAGEMENT_GUIDE.md new file mode 100644 index 0000000..1be288e --- /dev/null +++ b/documentation/TRANSLATION_MANAGEMENT_GUIDE.md @@ -0,0 +1,446 @@ +# Translation Management System Guide + +## ๐ŸŽฏ Overview + +The translation management system has been successfully modernized from legacy `window.TranslationClient` to modern Vue 3 composables. This provides: + +1. **โœ… Modern Vue 3 Composables** - Reactive translation state management +2. **โœ… Better Error Handling** - Comprehensive error states and fallbacks +3. **โœ… Loading States** - Built-in loading indicators for translations +4. **โœ… Batch Translation** - Efficient multiple text translation +5. **โœ… Backward Compatibility** - Existing code continues to work during migration + +## ๐Ÿ“ File Structure + +``` +eveai_chat_client/static/assets/js/ +โ”œโ”€โ”€ translation.js.old # Legacy TranslationClient (being phased out) +โ””โ”€โ”€ composables/ + โ”œโ”€โ”€ index.js # Barrel export for composables + โ””โ”€โ”€ useTranslation.js # Vue 3 translation composables +``` + +## ๐Ÿ”ง Available Composables + +### 1. useTranslation() - Full Featured + +The main composable providing complete translation functionality with reactive state management. + +```vue + +``` + +### 2. useTranslationClient() - Simplified + +Simplified composable for basic translation needs without reactive state management. + +```vue + +``` + +### 3. useReactiveTranslation() - Automatic Translation + +Composable for reactive text translation that automatically updates when language changes. + +```vue + + + +``` + +## ๐Ÿ”„ Migration Guide + +### From window.TranslationClient (Old) + +```vue + + +``` + +### To Vue 3 Composable (New) + +```vue + + +``` + +## ๐Ÿ“‹ Current Usage in Vue Components + +### Components Using window.TranslationClient + +1. **ChatInput.vue** - Lines 235-243: Placeholder translation +2. **MessageHistory.vue** - Lines 144-151: Message translation + +## โœ… Migration Examples + +### ChatInput.vue Migration + +**Before (Problematic):** +```vue + +``` + +**After (Modern Vue 3):** +```vue + +``` + +### MessageHistory.vue Migration + +**Before (Problematic):** +```vue + +``` + +**After (Modern Vue 3):** +```vue + +``` + +## ๐Ÿš€ Recommended Usage Patterns + +### For New Components +```vue + +``` + +### For Reactive Translation +```vue + + + +``` + +### For Batch Translation +```vue + +``` + +## ๐Ÿ” API Reference + +### useTranslation() + +**Returns:** +- `isTranslationReady: Ref` - Translation system availability +- `currentLanguage: ComputedRef` - Current language from chatConfig +- `isTranslating: Ref` - Loading state for translations +- `lastError: Ref` - Last translation error +- `translate(text, targetLang, sourceLang?, context?, apiPrefix?)` - Full translation method +- `translateSafe(text, targetLang, options?)` - Safe translation with fallback +- `translateBatch(texts, targetLang, options?)` - Batch translation +- `getCurrentLanguage()` - Get current language +- `getApiPrefix()` - Get API prefix + +### useTranslationClient() + +**Returns:** +- `translate` - Full translation method +- `translateSafe` - Safe translation with fallback +- `isTranslationReady` - Translation system availability +- `isTranslating` - Loading state +- `lastError` - Last error + +### useReactiveTranslation(text, options?) + +**Parameters:** +- `text: string` - Text to translate +- `options.context?: string` - Translation context +- `options.sourceLang?: string` - Source language +- `options.autoTranslate?: boolean` - Auto-translate on language change + +**Returns:** +- `translatedText: Ref` - Translated text +- `isLoading: Ref` - Loading state +- `updateTranslation(newLanguage?)` - Manual translation update + +## ๐Ÿ”ง Configuration + +### Translation Options + +```javascript +const options = { + sourceLang: 'en', // Source language (optional) + context: 'chat_message', // Translation context + apiPrefix: '/chat-client', // API prefix for tenant routing + fallbackText: 'Fallback' // Fallback text on error +}; +``` + +### Error Handling + +```vue + +``` + +## ๐Ÿ“ˆ Benefits Achieved + +1. **โœ… Modern Vue 3 Patterns** - Composition API and reactive state +2. **โœ… Better Error Handling** - Comprehensive error states and fallbacks +3. **โœ… Loading States** - Built-in loading indicators +4. **โœ… Type Safety Ready** - Prepared for TypeScript integration +5. **โœ… Batch Operations** - Efficient multiple text translation +6. **โœ… Reactive Translation** - Automatic updates on language changes +7. **โœ… Backward Compatibility** - Gradual migration support + +## ๐ŸŽ‰ Migration Status + +### โœ… Completed +- Modern Vue 3 composables created +- Barrel export updated +- Documentation completed +- Migration patterns established + +### ๐Ÿ”„ In Progress +- ChatInput.vue migration +- MessageHistory.vue migration + +### ๐Ÿ“‹ Next Steps +- Complete component migrations +- Remove legacy window.TranslationClient +- Verify all translations work correctly + +## ๐Ÿš€ Future Enhancements + +1. **TypeScript Support** - Add proper type definitions +2. **Caching System** - Cache translated texts for performance +3. **Offline Support** - Fallback for offline scenarios +4. **Translation Memory** - Remember previous translations +5. **Language Detection** - Automatic source language detection + +This modern translation system provides a solid foundation for scalable, maintainable translation management in the Vue 3 application! \ No newline at end of file diff --git a/eveai_chat_client/static/assets/js/MaterialIconManager.js b/eveai_chat_client/static/assets/js/MaterialIconManager.js deleted file mode 100644 index cf5cb1a..0000000 --- a/eveai_chat_client/static/assets/js/MaterialIconManager.js +++ /dev/null @@ -1,65 +0,0 @@ -// static/js/components/MaterialIconManager.js - -/** - * Een hulpklasse om Material Symbols Outlined iconen te beheren - * en dynamisch toe te voegen aan de pagina indien nodig. - */ -export const MaterialIconManager = { - name: 'MaterialIconManager', - data() { - return { - loadedIconSets: [], - defaultOptions: { - opsz: 24, // Optimale grootte: 24px - wght: 400, // Gewicht: normaal - FILL: 0, // Vulling: niet gevuld - GRAD: 0 // Kleurverloop: geen - } - }; - }, - methods: { - /** - * Zorgt ervoor dat de Material Symbols Outlined stijlbladen zijn geladen - * @param {Object} options - Opties voor het icoon (opsz, wght, FILL, GRAD) - * @param {Array} iconNames - Optionele lijst met specifieke iconen om te laden - */ - ensureIconsLoaded(options = {}, iconNames = []) { - const opts = { ...this.defaultOptions, ...options }; - const styleUrl = this.buildStyleUrl(opts, iconNames); - - // Controleer of deze specifieke set al is geladen - if (!this.loadedIconSets.includes(styleUrl)) { - this.loadStylesheet(styleUrl); - this.loadedIconSets.push(styleUrl); - } - }, - - /** - * Bouwt de URL voor het stijlblad - */ - buildStyleUrl(options, iconNames = []) { - let url = `https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@${options.opsz},${options.wght},${options.FILL},${options.GRAD}`; - - // Voeg specifieke iconNames toe als deze zijn opgegeven - if (iconNames.length > 0) { - url += `&icon_names=${iconNames.join(',')}`; - } - - return url; - }, - - /** - * Laadt een stijlblad dynamisch - */ - loadStylesheet(url) { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = url; - document.head.appendChild(link); - console.log(`Material Symbols Outlined geladen: ${url}`); - } - } -}; - -// Singleton instantie om te gebruiken in de hele applicatie -export const iconManager = new Vue(MaterialIconManager); diff --git a/eveai_chat_client/static/assets/js/composables/index.js b/eveai_chat_client/static/assets/js/composables/index.js new file mode 100644 index 0000000..01e77b8 --- /dev/null +++ b/eveai_chat_client/static/assets/js/composables/index.js @@ -0,0 +1,26 @@ +// eveai_chat_client/static/assets/js/composables/index.js + +/** + * Vue 3 Composables Barrel Export + * Provides easy access to all composables + */ + +// Icon Management Composables +export { + useIconManager, + useIcon, + useFormIcon +} from './useIconManager.js'; + +// Translation Management Composables +export { + useTranslation, + useTranslationClient, + useReactiveTranslation +} from './useTranslation.js'; + +// Future composables can be added here: +// export { useFormValidation } from './useFormValidation.js'; +// export { useChat } from './useChat.js'; + +console.log('Vue 3 composables loaded successfully'); \ No newline at end of file diff --git a/eveai_chat_client/static/assets/js/composables/useIconManager.js b/eveai_chat_client/static/assets/js/composables/useIconManager.js new file mode 100644 index 0000000..8e7fa6a --- /dev/null +++ b/eveai_chat_client/static/assets/js/composables/useIconManager.js @@ -0,0 +1,192 @@ +// eveai_chat_client/static/assets/js/composables/useIconManager.js + +import { onMounted, watch, ref } from 'vue'; + +/** + * Vue 3 Composable for Material Symbols Outlined icon management + * Self-contained modern icon management without legacy dependencies + */ +export function useIconManager() { + const isIconManagerReady = ref(true); // Always ready since we're self-contained + const loadedIcons = ref([]); + + /** + * Load a single Material Symbols Outlined icon + * @param {string} iconName - Name of the icon to load + * @param {Object} options - Icon options (opsz, wght, FILL, GRAD) + */ + const loadIcon = (iconName, options = {}) => { + if (!iconName) { + console.warn('No icon name provided'); + return; + } + + // Check if icon is already loaded + if (loadedIcons.value.includes(iconName)) { + return; + } + + // Default options for Material Symbols Outlined + const defaultOptions = { + opsz: 24, + wght: 400, + FILL: 0, + GRAD: 0 + }; + + const iconOptions = { ...defaultOptions, ...options }; + + // Create CSS for the icon with specific options + const cssRule = ` + .material-symbols-outlined.icon-${iconName} { + font-variation-settings: + 'FILL' ${iconOptions.FILL}, + 'wght' ${iconOptions.wght}, + 'GRAD' ${iconOptions.GRAD}, + 'opsz' ${iconOptions.opsz}; + } + `; + + // Add CSS rule to document + const style = document.createElement('style'); + style.textContent = cssRule; + document.head.appendChild(style); + + // Mark icon as loaded + loadedIcons.value.push(iconName); + + console.log(`Icon loaded: ${iconName}`); + }; + + /** + * Load multiple icons + * @param {Array} iconNames - Array of icon names to load + * @param {Object} options - Icon options (opsz, wght, FILL, GRAD) + */ + const loadIcons = (iconNames, options = {}) => { + if (!Array.isArray(iconNames)) { + console.warn('iconNames must be an array'); + return; + } + + iconNames.forEach(iconName => { + loadIcon(iconName, options); + }); + }; + + /** + * Ensure icons are loaded (alias for loadIcons for backward compatibility) + * @param {Object} options - Icon options (opsz, wght, FILL, GRAD) + * @param {Array} iconNames - Array of icon names to load + */ + const ensureIconsLoaded = (options = {}, iconNames = []) => { + if (iconNames && iconNames.length > 0) { + loadIcons(iconNames, options); + } + }; + + /** + * Watch a reactive property for icon changes and automatically load icons + * @param {Ref|Function} iconSource - Reactive source that contains icon name(s) + * @param {Object} options - Icon options + */ + const watchIcon = (iconSource, options = {}) => { + watch( + iconSource, + (newIcon) => { + if (newIcon) { + if (Array.isArray(newIcon)) { + loadIcons(newIcon, options); + } else { + loadIcon(newIcon, options); + } + } + }, + { immediate: true } + ); + }; + + /** + * Watch formData for icon changes (common pattern in Vue components) + * @param {Ref} formData - Reactive formData object + * @param {Object} options - Icon options + */ + const watchFormDataIcon = (formData, options = {}) => { + watch( + () => formData.value?.icon, + (newIcon) => { + if (newIcon) { + loadIcon(newIcon, options); + } + }, + { immediate: true } + ); + }; + + /** + * Preload common icons used throughout the application + * @param {Array} commonIcons - Array of commonly used icon names + * @param {Object} options - Icon options + */ + const preloadCommonIcons = (commonIcons = [], options = {}) => { + const defaultCommonIcons = [ + 'send', 'attach_file', 'mic', 'more_vert', 'close', 'check', + 'error', 'warning', 'info', 'expand_more', 'expand_less' + ]; + + const iconsToLoad = commonIcons.length > 0 ? commonIcons : defaultCommonIcons; + loadIcons(iconsToLoad, options); + }; + + return { + // State + isIconManagerReady, + + // Methods + loadIcon, + loadIcons, + ensureIconsLoaded, + + // Watchers + watchIcon, + watchFormDataIcon, + + // Utilities + preloadCommonIcons + }; +} + +/** + * Simplified composable for basic icon loading + * Use this when you only need basic icon loading functionality + */ +export function useIcon(iconName, options = {}) { + const { loadIcon, isIconManagerReady } = useIconManager(); + + onMounted(() => { + if (iconName) { + loadIcon(iconName, options); + } + }); + + return { + loadIcon, + isIconManagerReady + }; +} + +/** + * Composable for form-related icon management + * Automatically handles formData.icon watching + */ +export function useFormIcon(formData, options = {}) { + const { watchFormDataIcon, loadIcon, isIconManagerReady } = useIconManager(); + + // Automatically watch formData for icon changes + watchFormDataIcon(formData, options); + + return { + loadIcon, + isIconManagerReady + }; +} \ No newline at end of file diff --git a/eveai_chat_client/static/assets/js/composables/useTranslation.js b/eveai_chat_client/static/assets/js/composables/useTranslation.js new file mode 100644 index 0000000..e458e5a --- /dev/null +++ b/eveai_chat_client/static/assets/js/composables/useTranslation.js @@ -0,0 +1,233 @@ +// eveai_chat_client/static/assets/js/composables/useTranslation.js + +import { ref, computed, onMounted } from 'vue'; + +/** + * Vue 3 Composable for translation management + * Provides direct backend API communication for translations + */ +export function useTranslation() { + const isTranslationReady = ref(false); + const currentLanguage = ref('nl'); + const isTranslating = ref(false); + const lastError = ref(null); + + // Check if translation system is available + const checkTranslationReady = () => { + // Translation is altijd ready omdat we de backend API gebruiken + // Controleer alleen of we in een browser environment zijn + if (typeof window !== 'undefined' && typeof fetch !== 'undefined') { + isTranslationReady.value = true; + return true; + } + return false; + }; + + onMounted(() => { + // Eenvoudige check - geen retry mechanism nodig + checkTranslationReady(); + }); + + /** + * Translate text to target language + * @param {string} text - Text to translate + * @param {string} targetLang - Target language code + * @param {string|null} sourceLang - Source language code (optional) + * @param {string|null} context - Translation context (optional) + * @param {string} apiPrefix - API prefix for tenant routing + * @returns {Promise} Translation result + */ + const translate = async (text, targetLang, sourceLang = null, context = null, apiPrefix = '') => { + if (!text || !text.trim()) { + const error = new Error('No text provided for translation'); + lastError.value = error; + throw error; + } + + isTranslating.value = true; + lastError.value = null; + + try { + // Bepaal de juiste API URL + const baseUrl = apiPrefix || window.chatConfig?.apiPrefix || ''; + const apiUrl = `${baseUrl}/api/translate`; + + // Maak de request payload + const payload = { + text: text, + target_lang: targetLang + }; + + // Voeg optionele parameters toe + if (sourceLang) payload.source_lang = sourceLang; + if (context) payload.context = context; + + // Maak de HTTP request + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }, + credentials: 'same-origin', // Voor sessie cookies + body: JSON.stringify(payload) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + + // Update current language if translation was successful + if (result.success) { + currentLanguage.value = targetLang; + } + + return result; + } catch (error) { + console.error('Translation error in composable:', error); + lastError.value = error; + throw error; + } finally { + isTranslating.value = false; + } + }; + + /** + * Translate text with automatic error handling and loading state + * @param {string} text - Text to translate + * @param {string} targetLang - Target language code + * @param {Object} options - Translation options + * @returns {Promise} Translated text or null on error + */ + const translateSafe = async (text, targetLang, options = {}) => { + const { + sourceLang = null, + context = null, + apiPrefix = '', + fallbackText = text + } = options; + + try { + const result = await translate(text, targetLang, sourceLang, context, apiPrefix); + return result.success ? result.translated_text : fallbackText; + } catch (error) { + console.warn('Safe translation failed, using fallback:', error.message); + return fallbackText; + } + }; + + /** + * Batch translate multiple texts + * @param {Array} texts - Array of texts to translate + * @param {string} targetLang - Target language code + * @param {Object} options - Translation options + * @returns {Promise>} Array of translated texts + */ + const translateBatch = async (texts, targetLang, options = {}) => { + const results = await Promise.allSettled( + texts.map(text => translateSafe(text, targetLang, options)) + ); + + return results.map((result, index) => + result.status === 'fulfilled' ? result.value : texts[index] + ); + }; + + /** + * Get current language from chatConfig or fallback + */ + const getCurrentLanguage = () => { + return window.chatConfig?.language || currentLanguage.value || 'nl'; + }; + + /** + * Get API prefix from chatConfig or fallback + */ + const getApiPrefix = () => { + return window.chatConfig?.apiPrefix || ''; + }; + + return { + // State + isTranslationReady, + currentLanguage: computed(() => getCurrentLanguage()), + isTranslating, + lastError, + + // Methods + translate, + translateSafe, + translateBatch, + + // Utilities + getCurrentLanguage, + getApiPrefix + }; +} + +/** + * Simplified composable for basic translation needs + * Use this when you only need simple text translation + */ +export function useTranslationClient() { + const { translate, translateSafe, isTranslationReady, isTranslating, lastError } = useTranslation(); + + return { + translate, + translateSafe, + isTranslationReady, + isTranslating, + lastError + }; +} + +/** + * Composable for reactive text translation + * Automatically translates text when language changes + */ +export function useReactiveTranslation(text, options = {}) { + const { translateSafe, currentLanguage } = useTranslation(); + const translatedText = ref(text); + const isLoading = ref(false); + + const { + context = null, + sourceLang = null, + autoTranslate = true + } = options; + + // Watch for language changes and auto-translate + if (autoTranslate) { + // We'll implement this when we have proper reactivity setup + // For now, provide manual translation method + } + + const updateTranslation = async (newLanguage = null) => { + const targetLang = newLanguage || currentLanguage.value; + + if (!text || targetLang === sourceLang) { + translatedText.value = text; + return; + } + + isLoading.value = true; + try { + const result = await translateSafe(text, targetLang, { + sourceLang, + context, + apiPrefix: window.chatConfig?.apiPrefix || '' + }); + translatedText.value = result; + } finally { + isLoading.value = false; + } + }; + + return { + translatedText, + isLoading, + updateTranslation + }; +} \ No newline at end of file diff --git a/eveai_chat_client/static/assets/js/composables/useTranslation.js.backup b/eveai_chat_client/static/assets/js/composables/useTranslation.js.backup new file mode 100644 index 0000000..3ab1839 --- /dev/null +++ b/eveai_chat_client/static/assets/js/composables/useTranslation.js.backup @@ -0,0 +1,238 @@ +// eveai_chat_client/static/assets/js/composables/useTranslation.js + +import { ref, computed, onMounted } from 'vue'; + +/** + * Vue 3 Composable for translation management + * Provides modern alternative to window.TranslationClient + */ +export function useTranslation() { + const isTranslationReady = ref(false); + const currentLanguage = ref('nl'); + const isTranslating = ref(false); + const lastError = ref(null); + + // Check if translation system is available with retry mechanism + const checkTranslationReady = () => { + if (window.TranslationClient && typeof window.TranslationClient.translate === 'function') { + isTranslationReady.value = true; + return true; + } + return false; + }; + + onMounted(() => { + // Initial check + if (checkTranslationReady()) { + return; + } + + // Retry mechanism - wait for TranslationClient to become available + let retryCount = 0; + const maxRetries = 10; + const retryInterval = 100; // 100ms + + const retryCheck = () => { + if (checkTranslationReady()) { + return; // Success! + } + + retryCount++; + if (retryCount < maxRetries) { + setTimeout(retryCheck, retryInterval); + } else { + console.warn('TranslationClient is not available after retries'); + isTranslationReady.value = false; + } + }; + + // Start retry process + setTimeout(retryCheck, retryInterval); + }); + + /** + * Translate text to target language + * @param {string} text - Text to translate + * @param {string} targetLang - Target language code + * @param {string|null} sourceLang - Source language code (optional) + * @param {string|null} context - Translation context (optional) + * @param {string} apiPrefix - API prefix for tenant routing + * @returns {Promise} Translation result + */ + const translate = async (text, targetLang, sourceLang = null, context = null, apiPrefix = '') => { + if (!isTranslationReady.value || !window.TranslationClient) { + const error = new Error('Translation system not ready'); + lastError.value = error; + throw error; + } + + if (!text || !text.trim()) { + const error = new Error('No text provided for translation'); + lastError.value = error; + throw error; + } + + isTranslating.value = true; + lastError.value = null; + + try { + const result = await window.TranslationClient.translate( + text, + targetLang, + sourceLang, + context, + apiPrefix + ); + + // Update current language if translation was successful + if (result.success) { + currentLanguage.value = targetLang; + } + + return result; + } catch (error) { + console.error('Translation error in composable:', error); + lastError.value = error; + throw error; + } finally { + isTranslating.value = false; + } + }; + + /** + * Translate text with automatic error handling and loading state + * @param {string} text - Text to translate + * @param {string} targetLang - Target language code + * @param {Object} options - Translation options + * @returns {Promise} Translated text or null on error + */ + const translateSafe = async (text, targetLang, options = {}) => { + const { + sourceLang = null, + context = null, + apiPrefix = '', + fallbackText = text + } = options; + + try { + const result = await translate(text, targetLang, sourceLang, context, apiPrefix); + return result.success ? result.translated_text : fallbackText; + } catch (error) { + console.warn('Safe translation failed, using fallback:', error.message); + return fallbackText; + } + }; + + /** + * Batch translate multiple texts + * @param {Array} texts - Array of texts to translate + * @param {string} targetLang - Target language code + * @param {Object} options - Translation options + * @returns {Promise>} Array of translated texts + */ + const translateBatch = async (texts, targetLang, options = {}) => { + const results = await Promise.allSettled( + texts.map(text => translateSafe(text, targetLang, options)) + ); + + return results.map((result, index) => + result.status === 'fulfilled' ? result.value : texts[index] + ); + }; + + /** + * Get current language from chatConfig or fallback + */ + const getCurrentLanguage = () => { + return window.chatConfig?.language || currentLanguage.value; + }; + + /** + * Get API prefix from chatConfig or fallback + */ + const getApiPrefix = () => { + return window.chatConfig?.apiPrefix || ''; + }; + + return { + // State + isTranslationReady, + currentLanguage: computed(() => getCurrentLanguage()), + isTranslating, + lastError, + + // Methods + translate, + translateSafe, + translateBatch, + + // Utilities + getCurrentLanguage, + getApiPrefix + }; +} + +/** + * Simplified composable for basic translation needs + * Use this when you only need simple text translation + */ +export function useTranslationClient() { + const { translate, translateSafe, isTranslationReady, isTranslating, lastError } = useTranslation(); + + return { + translate, + translateSafe, + isTranslationReady, + isTranslating, + lastError + }; +} + +/** + * Composable for reactive text translation + * Automatically translates text when language changes + */ +export function useReactiveTranslation(text, options = {}) { + const { translateSafe, currentLanguage } = useTranslation(); + const translatedText = ref(text); + const isLoading = ref(false); + + const { + context = null, + sourceLang = null, + autoTranslate = true + } = options; + + // Watch for language changes and auto-translate + if (autoTranslate) { + // We'll implement this when we have proper reactivity setup + // For now, provide manual translation method + } + + const updateTranslation = async (newLanguage = null) => { + const targetLang = newLanguage || currentLanguage.value; + + if (!text || targetLang === sourceLang) { + translatedText.value = text; + return; + } + + isLoading.value = true; + try { + const result = await translateSafe(text, targetLang, { + sourceLang, + context, + apiPrefix: window.chatConfig?.apiPrefix || '' + }); + translatedText.value = result; + } finally { + isLoading.value = false; + } + }; + + return { + translatedText, + isLoading, + updateTranslation + }; +} \ No newline at end of file diff --git a/eveai_chat_client/static/assets/js/iconManager.js b/eveai_chat_client/static/assets/js/iconManager.js deleted file mode 100644 index 9dbedc6..0000000 --- a/eveai_chat_client/static/assets/js/iconManager.js +++ /dev/null @@ -1,130 +0,0 @@ -// static/js/iconManager.js - -/** - * Een eenvoudige standalone icon manager voor Material Symbols Outlined - * Deze kan direct worden gebruikt zonder Vue - */ -window.iconManager = { - loadedIcons: [], - - /** - * Laadt een Material Symbols Outlined icoon als het nog niet is geladen - * @param {string} iconName - Naam van het icoon - * @param {Object} options - Opties voor het icoon (opsz, wght, FILL, GRAD) - */ - loadIcon: function(iconName, options = {}) { - if (!iconName) return; - - if (this.loadedIcons.includes(iconName)) { - return; // Icoon is al geladen - } - - const defaultOptions = { - opsz: 24, - wght: 400, - FILL: 0, - GRAD: 0 - }; - - const opts = { ...defaultOptions, ...options }; - - // Genereer unieke ID voor het stylesheet element - const styleId = `material-symbols-${iconName}`; - - // Controleer of het stylesheet al bestaat - if (!document.getElementById(styleId)) { - const link = document.createElement('link'); - link.id = styleId; - link.rel = 'stylesheet'; - link.href = `https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@${opts.opsz},${opts.wght},${opts.FILL},${opts.GRAD}&icon_names=${iconName}`; - document.head.appendChild(link); - console.log(`Material Symbol geladen: ${iconName}`); - - this.loadedIcons.push(iconName); - } - }, - - /** - * Laadt een set van Material Symbols Outlined iconen - * @param {Array} iconNames - Array met icoonnamen - * @param {Object} options - Opties voor de iconen - */ - loadIcons: function(iconNames, options = {}) { - if (!iconNames || !Array.isArray(iconNames) || iconNames.length === 0) { - return; - } - - // Filter alleen iconen die nog niet zijn geladen - const newIcons = iconNames.filter(icon => !this.loadedIcons.includes(icon)); - - if (newIcons.length === 0) { - return; // Alle iconen zijn al geladen - } - - const defaultOptions = { - opsz: 24, - wght: 400, - FILL: 0, - GRAD: 0 - }; - - const opts = { ...defaultOptions, ...options }; - - // Genereer unieke ID voor het stylesheet element - const styleId = `material-symbols-set-${newIcons.join('-')}`; - - // Controleer of het stylesheet al bestaat - if (!document.getElementById(styleId)) { - const link = document.createElement('link'); - link.id = styleId; - link.rel = 'stylesheet'; - link.href = `https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@${opts.opsz},${opts.wght},${opts.FILL},${opts.GRAD}&icon_names=${newIcons.join(',')}`; - document.head.appendChild(link); - console.log(`Material Symbols geladen: ${newIcons.join(', ')}`); - - // Voeg de nieuwe iconen toe aan de geladen lijst - this.loadedIcons.push(...newIcons); - } - } -}; - -// Export de iconManager functie om te gebruiken in Vue componenten -// Dit vervangt de complexe injectie in het DynamicForm component -export { iconManager as default }; - -// We exporteren iconManager als default, maar houden ook de window.iconManager beschikbaar -// voor backwards compatibility met bestaande code - -// Maak een Vue mixin die iconManager toevoegt aan elk component dat het nodig heeft -export const IconManagerMixin = { - created() { - // Check of er een formData.icon property is - if (this.formData && this.formData.icon) { - window.iconManager.loadIcon(this.formData.icon); - } - }, - - // Watch voor formData.icon veranderingen - watch: { - 'formData.icon': function(newIcon) { - if (newIcon) { - window.iconManager.loadIcon(newIcon); - } - } - }, - - // Methode om toe te voegen aan componenten - methods: { - loadIcon(iconName, options) { - window.iconManager.loadIcon(iconName, options); - }, - - loadIcons(iconNames, options) { - window.iconManager.loadIcons(iconNames, options); - } - } -}; - -// We hoeven niet langer DynamicForm te manipuleren -// omdat Vue componenten nu de IconManagerMixin kunnen gebruiken -console.log('IconManager en IconManagerMixin zijn beschikbaar voor componenten'); diff --git a/eveai_chat_client/static/assets/js/translation.js b/eveai_chat_client/static/assets/js/translation.js deleted file mode 100644 index 8269bc4..0000000 --- a/eveai_chat_client/static/assets/js/translation.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * EveAI Vertaal API Client - * Functies voor het vertalen van tekst via de EveAI API - */ - -const TranslationClient = { - /** - * Vertaalt een tekst naar de opgegeven doeltaal - * - * @param {string} text - De te vertalen tekst - * @param {string} targetLang - ISO 639-1 taalcode van de doeltaal - * @param {string|null} sourceLang - (Optioneel) ISO 639-1 taalcode van de brontaal - * @param {string|null} context - (Optioneel) Context voor de vertaling - * @param {string|null} apiPrefix - (Optioneel) API prefix voor tenant routing - * @returns {Promise} - Een promise met het vertaalresultaat - */ - translate: async function(text, targetLang, sourceLang = null, context = null, apiPrefix = '') { - try { - // Voorbereiding van de aanvraagdata - const requestData = { - text: text, - target_lang: targetLang - }; - - // Voeg optionele parameters toe indien aanwezig - if (sourceLang) requestData.source_lang = sourceLang; - if (context) requestData.context = context; - - // Bouw de juiste endpoint URL met prefix - const endpoint = `${apiPrefix}/api/translate`; - console.log(`Vertaling aanvragen op endpoint: ${endpoint}`); - - // Doe het API-verzoek - const response = await fetch(endpoint, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestData) - }); - - // Controleer of het verzoek succesvol was - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.error || 'Onbekende fout bij vertalen'); - } - - // Verwerk het resultaat - return await response.json(); - - } catch (error) { - console.error('Vertaalfout:', error); - throw error; - } - } -}; - -// Maak TranslationClient globaal beschikbaar -window.TranslationClient = TranslationClient; - -// Geen auto-initialisatie meer nodig -// De Vue-based LanguageSelector component neemt deze taak over -console.log('TranslationClient geladen en klaar voor gebruik'); diff --git a/eveai_chat_client/static/assets/vue-components/ChatInput.vue b/eveai_chat_client/static/assets/vue-components/ChatInput.vue index 99e3d8e..de39023 100644 --- a/eveai_chat_client/static/assets/vue-components/ChatInput.vue +++ b/eveai_chat_client/static/assets/vue-components/ChatInput.vue @@ -62,15 +62,26 @@ + + \ No newline at end of file diff --git a/eveai_chat_client/static/assets/vue-components/SideBarExplanation.vue b/eveai_chat_client/static/assets/vue-components/SideBarExplanation.vue new file mode 100644 index 0000000..40b234a --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/SideBarExplanation.vue @@ -0,0 +1,129 @@ + + + + + + \ No newline at end of file diff --git a/eveai_chat_client/static/assets/vue-components/SideBarLogo.vue b/eveai_chat_client/static/assets/vue-components/SideBarLogo.vue new file mode 100644 index 0000000..071c83e --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/SideBarLogo.vue @@ -0,0 +1,73 @@ + + + + + + \ No newline at end of file diff --git a/eveai_chat_client/static/assets/vue-components/SideBarMakeName.vue b/eveai_chat_client/static/assets/vue-components/SideBarMakeName.vue new file mode 100644 index 0000000..23851af --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/SideBarMakeName.vue @@ -0,0 +1,42 @@ + + + + + + \ No newline at end of file diff --git a/eveai_chat_client/templates/base.html b/eveai_chat_client/templates/base.html index 8ba434c..25a93cd 100644 --- a/eveai_chat_client/templates/base.html +++ b/eveai_chat_client/templates/base.html @@ -27,17 +27,8 @@
- - + +
diff --git a/frontend_src/js/chat-client.js b/frontend_src/js/chat-client.js index cded11e..5d8dec5 100644 --- a/frontend_src/js/chat-client.js +++ b/frontend_src/js/chat-client.js @@ -20,10 +20,6 @@ import { marked } from 'marked'; window.Vue = { createApp }; window.marked = marked; -// Support tools -import iconManager, { IconManagerMixin } from '../../../eveai_chat_client/static/assets/js/iconManager.js'; -import '../../../eveai_chat_client/static/assets/js/translation.js'; - // Gebruik barrel export voor componenten import * as Components from '../../../eveai_chat_client/static/assets/js/components/index.js'; diff --git a/nginx/frontend_src/js/chat-client.js b/nginx/frontend_src/js/chat-client.js index 476a475..7f455aa 100644 --- a/nginx/frontend_src/js/chat-client.js +++ b/nginx/frontend_src/js/chat-client.js @@ -17,10 +17,6 @@ import { FormField } from '../../../../../../../../../Users/josako/Library/Appli window.Vue = { createApp, version }; window.marked = marked; -// Support tools -import '../../../eveai_chat_client/static/assets/js/iconManager.js'; -import '../../../eveai_chat_client/static/assets/js/translation.js'; - // Gebruik barrel export voor componenten import * as Components from '../../../eveai_chat_client/static/assets/vue-components/index.js'; @@ -31,6 +27,7 @@ console.log('Components loaded:', Object.keys(Components)); // Import specifieke componenten import LanguageSelector from '../../../eveai_chat_client/static/assets/vue-components/LanguageSelector.vue'; import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue'; +import SideBar from '../../../eveai_chat_client/static/assets/vue-components/SideBar.vue'; // Globale Vue error tracking window.addEventListener('error', function(event) { @@ -45,89 +42,55 @@ document.addEventListener('DOMContentLoaded', function() { window.chatConfig = {}; } - // Vul de sidebar explanation in - fillSidebarExplanation(); - - // Initialiseer language selector - initializeLanguageSelector(); + // Initialiseer sidebar (vervangt fillSidebarExplanation en initializeLanguageSelector) + initializeSidebar(); // Initialiseer chat app (simpel) initializeChatApp(); }); /** - * Vul de sidebar explanation in + * Initialiseert de sidebar component */ -function fillSidebarExplanation() { - const sidebarElement = document.getElementById('sidebar-explanation'); - if (sidebarElement && window.chatConfig.explanation) { - if (typeof window.marked === 'function') { - sidebarElement.innerHTML = window.marked(window.chatConfig.explanation); - } else if (window.marked && typeof window.marked.parse === 'function') { - sidebarElement.innerHTML = window.marked.parse(window.chatConfig.explanation.replace(/\[\[(.*?)\]\]/g, '$1')); - } else { - sidebarElement.innerHTML = window.chatConfig.explanation; - } - } -} - -/** - * Initialiseert de language selector - */ -function initializeLanguageSelector() { - const container = document.getElementById('language-selector-container'); - +function initializeSidebar() { + const container = document.getElementById('sidebar-container'); + if (!container) { - console.error('#language-selector-container niet gevonden'); + console.error('#sidebar-container niet gevonden'); return; } try { // Maak props voor de component const props = { + tenantMake: { + name: window.chatConfig.tenantMake?.name || '', + logo_url: window.chatConfig.tenantMake?.logo_url || '', + subtitle: window.chatConfig.tenantMake?.subtitle || '' + }, + explanationText: window.chatConfig.explanation || '', initialLanguage: window.chatConfig.language || 'nl', supportedLanguageDetails: window.chatConfig.supportedLanguageDetails || {}, - allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'] + allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'], + apiPrefix: window.chatConfig.apiPrefix || '' }; - // Mount de component direct - BELANGRIJK: we gebruiken window.Vue als dat beschikbaar is - // Dit is nodig voor compatibiliteit met oude code - const app = window.Vue && typeof window.Vue.createApp === 'function' - ? window.Vue.createApp(LanguageSelector, props) - : createApp(LanguageSelector, props); - - // Registreer error handler + // Mount de component + const app = createApp(SideBar, props); + + // Error handler app.config.errorHandler = (err, vm, info) => { - console.error('๐Ÿšจ [Vue Error]', err); + console.error('๐Ÿšจ [Vue Error in Sidebar]', err); console.error('Component:', vm); console.error('Error Info:', info); }; - // Mount de component const mountedApp = app.mount(container); - - // Language change event listener - document.addEventListener('vue:language-changed', function(event) { - const newLanguage = event.detail.language; - console.log(`Taal gewijzigd naar ${newLanguage}`); - - // Update chatConfig - if (window.chatConfig) { - window.chatConfig.language = newLanguage; - } - - // Stuur event voor andere componenten - const globalEvent = new CustomEvent('language-changed', { - detail: { language: newLanguage } - }); - document.dispatchEvent(globalEvent); - - // Sla voorkeur op - localStorage.setItem('preferredLanguage', newLanguage); - }); + + console.log('โœ… Sidebar component successfully mounted'); + return mountedApp; } catch (error) { - console.error('๐Ÿšจ [CRITICAL ERROR] Bij initialiseren language selector:', error); - console.error('Stack trace:', error.stack); + console.error('๐Ÿšจ [CRITICAL ERROR] Bij initialiseren sidebar:', error); } } diff --git a/nginx/static/dist/chat-client.css b/nginx/static/dist/chat-client.css index f6aa7ba..7fd9290 100644 --- a/nginx/static/dist/chat-client.css +++ b/nginx/static/dist/chat-client.css @@ -1 +1 @@ -:root{--primary-color:#007bff;--secondary-color:#6c757d;--background-color:#fff;--text-color:#212529;--sidebar-color:#f8f9fa;--message-user-bg:#e9f5ff;--message-bot-bg:#f8f9fa;--border-radius:8px;--spacing:16px}.app-container{width:100%;height:100vh;display:flex}.sidebar{background-color:var(--sidebar-background);flex-direction:column;width:300px;padding:20px;display:flex;overflow-y:auto}.sidebar-logo{text-align:center;margin-bottom:20px}.sidebar-logo img{max-width:100%;max-height:100px}.sidebar-make-name{text-align:center;margin-bottom:20px;font-size:24px;font-weight:700}.sidebar-explanation{background-color:var(--markdown-background-color);color:var(--markdown-text-color);border-radius:5px;margin-top:20px;padding:10px;overflow-y:auto}.sidebar-explanation *{color:inherit}.sidebar-explanation a{color:var(--primary-color);text-decoration:underline}.sidebar-explanation ul,.sidebar-explanation ol{margin:10px 0;padding-left:20px}.sidebar-explanation li{margin-bottom:5px}.sidebar-explanation ul li{list-style-type:disc}.sidebar-explanation ol li{list-style-type:decimal}.content-area{background:linear-gradient(135deg,var(--gradient-start-color),var(--gradient-end-color));flex-direction:column;flex:1;display:flex;overflow-y:auto}body{color:var(--text-color);background-color:var(--background-color);height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;line-height:1.6;overflow:hidden}.container{width:100%;height:100vh}.chat-container{flex-direction:column;flex:1;height:100%;min-height:0;display:flex}.sidebar{background-color:var(--sidebar-background);width:280px;padding:var(--spacing);border-right:1px solid #0000001a;flex-direction:column;display:flex;overflow-y:auto}.logo{margin-bottom:var(--spacing);text-align:center}.logo img{max-width:100%;max-height:60px}.sidebar-content{flex-direction:column;flex:1;display:flex}.sidebar-text{margin-bottom:var(--spacing)}.team-info{padding-top:var(--spacing);border-top:1px solid #0000001a;margin-top:auto}.team-member{align-items:center;margin-bottom:8px;display:flex}.team-member img{border-radius:50%;width:32px;height:32px;margin-right:8px}.chat-main{flex-direction:column;flex:1;height:100%;display:flex}.chat-header{padding:var(--spacing);border-bottom:1px solid #0000001a}.language-change-indicator{background-color:rgba(var(--primary-color-rgb,0,123,255),.2);color:#fff;text-align:center;border-radius:4px;margin-bottom:10px;padding:5px 8px;font-size:.9em;animation:3s ease-in-out fadeInOut}@keyframes fadeInOut{0%{opacity:0}10%{opacity:1}90%{opacity:1}to{opacity:0}}.language-change-indicator.success{color:#155724;background:#d4edda}.language-change-indicator.error{color:#721c24;background:#f8d7da}.user-message{float:right}.bot-message{float:left}.user-message .message-content{background-color:var(--message-user-bg);color:var(--text-color)}.bot-message .message-content{background-color:var(--message-bot-bg);color:var(--text-color)}#chat-input{border-radius:var(--border-radius);resize:none;border:1px solid #0003;flex:1;height:60px;margin-right:8px;padding:12px}.error-container{justify-content:center;align-items:center;height:100vh;display:flex}.error-box{border-radius:var(--border-radius);text-align:center;background-color:#fff;max-width:500px;padding:2rem;box-shadow:0 4px 6px #0000001a}.error-message{color:#dc3545;margin:1rem 0}.error-actions{margin-top:1.5rem}.chat-app-container{box-sizing:border-box;flex-direction:column;width:100%;height:100%;min-height:0;padding:20px;display:flex}.chat-component-container{flex-direction:column;flex:1;width:100%;max-width:1000px;margin-left:auto;margin-right:auto;display:flex}.chat-messages-area{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffff1a;border-radius:15px;flex-direction:column;flex:1;align-self:center;width:100%;max-width:1000px;min-height:0;margin-bottom:20px;margin-left:auto;margin-right:auto;display:flex;overflow:hidden;box-shadow:0 4px 20px #0000001a}.chat-input-area{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:10;background:#ffffff26;border:1px solid #fff3;border-radius:15px;flex:none;align-self:center;width:100%;max-width:1000px;margin-left:auto;margin-right:auto;box-shadow:0 4px 20px #0000001a}.message-history-container{box-sizing:border-box;flex-direction:column;width:100%;max-width:1000px;height:100%;min-height:0;margin-left:auto;margin-right:auto;padding:20px;display:flex}.chat-messages{scroll-behavior:smooth;flex:1;margin-right:-10px;padding-right:10px;overflow-y:auto}.chat-input-container{box-sizing:border-box;width:100%;max-width:1000px;margin-left:auto;margin-right:auto;padding:20px;position:relative}.chat-input{background:#fff;border:1px solid #0000000d;border-radius:15px;align-items:flex-end;gap:12px;padding:20px;display:flex;box-shadow:0 2px 15px #0000001a}.message-input{resize:none;box-sizing:border-box;border:1px solid #ddd;border-radius:25px;outline:none;width:100%;min-height:45px;max-height:120px;padding:12px 18px;font-family:inherit;font-size:14px;line-height:1.4;transition:all .2s}.message-input:focus{border-color:var(--primary-color);box-shadow:0 0 0 3px #007bff1a}.message-input.over-limit{background-color:#dc35450d;border-color:#dc3545}.send-btn{background:var(--primary-color);color:#fff;cursor:pointer;border:none;border-radius:50%;justify-content:center;align-items:center;width:45px;height:45px;font-size:18px;transition:all .2s;display:flex;box-shadow:0 2px 10px #0000001a}.send-btn:hover:not(:disabled){background:var(--secondary-color);transform:scale(1.05);box-shadow:0 4px 15px #0003}.send-btn:disabled{cursor:not-allowed;box-shadow:none;background:#ccc;transform:none}.character-counter{color:#666;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);background:#ffffffe6;border-radius:10px;padding:2px 6px;font-size:12px;position:absolute;bottom:-25px;right:15px}.character-counter.over-limit{color:#dc3545;background:#dc35451a;font-weight:700}.loading-spinner{font-size:16px;animation:1s linear infinite spin}@media (width<=768px){.chat-app-container{padding:10px}.chat-messages-area{max-width:100%;margin-bottom:15px}.chat-input-area{max-width:100%}.message-history-container,.chat-input-container{max-width:100%;padding:15px}.chat-input{gap:10px;padding:15px}.action-btn{width:40px;height:40px;font-size:16px}.message-input{min-height:40px;padding:10px 15px;font-size:16px}.chat-component-container{max-width:100%}}@media (width<=480px){.chat-app-container{padding:8px}.chat-messages-area{margin-bottom:12px}.message-history-container,.chat-input-container{padding:12px}}.chat-input.loading .message-input{opacity:.7}.chat-input.loading .action-btn{animation:1.5s infinite pulse}@keyframes pulse{0%{opacity:1}50%{opacity:.5}to{opacity:1}}.chat-messages::-webkit-scrollbar{width:6px}.chat-messages::-webkit-scrollbar-track{background:#0000001a;border-radius:3px}.chat-messages::-webkit-scrollbar-thumb{background:#0000004d;border-radius:3px}.chat-messages::-webkit-scrollbar-thumb:hover{background:#00000080}.message-text:empty{display:none}.progress-tracker .status-icon.error{color:#f44336}.progress-tracker.error .progress-header{background-color:#f443361a;border-color:#f44336}.message-content:has(.message-text:empty) .message-progress{margin-bottom:0}.message-content:has(.message-text:empty):not(:has(.message-progress.completed)):not(:has(.message-progress.error)){box-shadow:none;background:0 0;border:none;margin:0;padding:0}.chat-input:focus-within{border-color:#007bff4d;box-shadow:0 2px 20px #007bff33}.chat-messages-area,.chat-input-area{transition:all .3s}.chat-messages-area:hover,.chat-input-area:hover{box-shadow:0 6px 25px #00000026}.message{clear:both;margin-bottom:16px;padding:0 20px;animation:.3s ease-out messageSlideIn;display:flex}.message.user{justify-content:flex-end}.message.ai,.message.bot{justify-content:flex-start}.message-content{word-wrap:break-word;border-radius:18px;max-width:70%;padding:12px 16px;transition:all .2s;display:inline-block;position:relative;box-shadow:0 2px 8px #0000001a}.message.user .message-content{color:#fff;background:#0000001a;border-bottom-right-radius:4px}.message.ai .message-content,.message.bot .message-content{color:#212529;background:#ffffff1a;border-bottom-left-radius:4px;margin-right:60px}.message-text{margin-bottom:6px;font-size:14px;line-height:1.4}.message-text p{margin:0}.message-text p+p{margin-top:8px}.btn-small{cursor:pointer;border:none;border-radius:4px;padding:4px 12px;font-size:12px;transition:all .2s}.btn-primary{color:#fff;background:#007bff}.btn-primary:hover{background:#0056b3}.btn-secondary{color:#fff;background:#6c757d}.btn-secondary:hover{background:#545b62}.form-message{justify-content:center;margin:20px 0}.form-message .message-content{background:#fff;border:1px solid #e9ecef;border-radius:12px;max-width:90%;padding:20px;box-shadow:0 4px 12px #0000001a}.system-message{text-align:center;color:#6c757d;background:#6c757d1a;border-radius:20px;justify-content:center;align-items:center;gap:8px;max-width:80%;margin:10px auto;padding:8px 16px;font-size:13px;display:flex}.system-icon{font-size:14px}.error-message{color:#721c24;background:#f8d7da;border:1px solid #f5c6cb;border-radius:8px;justify-content:space-between;align-items:center;gap:12px;max-width:80%;margin:10px auto;padding:12px 16px;display:flex}.error-icon{color:#dc3545;font-size:16px}.retry-btn{color:#fff;cursor:pointer;background:#dc3545;border:none;border-radius:4px;padding:4px 12px;font-size:12px;transition:background-color .2s}.retry-btn:hover{background:#c82333}.message-reactions{flex-wrap:wrap;gap:4px;margin-top:8px;display:flex}.reaction{cursor:pointer;background:#0000000d;border:1px solid #0000001a;border-radius:12px;padding:2px 8px;font-size:12px;transition:all .2s}.reaction:hover{background:#0000001a;transform:scale(1.05)}.message-image{cursor:pointer;border-radius:8px;max-width:100%;max-height:300px;margin-bottom:8px;transition:transform .2s}.message-image:hover{transform:scale(1.02)}.image-caption{opacity:.9;margin-bottom:6px;font-size:13px}.file-attachment{background:#00000008;border-radius:8px;align-items:center;gap:12px;margin-bottom:8px;padding:12px;display:flex}.file-icon{font-size:24px}.file-info{flex:1}.file-name{margin-bottom:2px;font-weight:500}.file-size{opacity:.7;font-size:12px}.file-download{cursor:pointer;font-size:20px;text-decoration:none;transition:transform .2s}.file-download:hover{transform:scale(1.1)}.message-content:hover{transform:translateY(-1px);box-shadow:0 4px 12px #00000026}@keyframes messageSlideIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.empty-state{text-align:center;color:#6c757d;padding:40px 20px}.empty-icon{opacity:.5;margin-bottom:16px;font-size:48px}.empty-text{margin-bottom:8px;font-size:18px;font-weight:500}.empty-subtext{opacity:.8;font-size:14px}@media (width<=768px){.message{padding:0 15px}.message-content{max-width:85%;padding:10px 14px;font-size:14px}.message.user .message-content{margin-left:40px}.message.ai .message-content,.message.bot .message-content{margin-right:40px}}@media (width<=480px){.message{padding:0 10px}.message-content{max-width:90%;margin-left:20px!important;margin-right:20px!important}}.progress-tracker{background:#f8f9fa;border-radius:8px;margin:8px 0;font-size:13px;transition:all .3s;overflow:hidden}.progress-tracker.expanded{max-height:200px}.progress-tracker.completed{background:#9bff9b1a}.progress-header{cursor:pointer;background:#00000005;border-bottom:1px solid #0000;justify-content:space-between;align-items:center;padding:8px 12px;transition:all .2s;display:flex}.progress-header:hover{background:#0000000d}.progress-tracker.expanded .progress-header{border-bottom-color:#e9ecef}.progress-title{color:#495057;align-items:center;gap:8px;font-weight:500;display:flex}.status-icon{border-radius:50%;width:12px;height:12px;display:inline-block;position:relative}.status-icon.completed{color:#fff;text-align:center;background:#28a745;font-size:8px;line-height:12px}.status-icon.in-progress{background:#007bff;animation:1.5s infinite pulse}.spinner{border:2px solid #f3f3f3;border-top-color:#007bff;border-radius:50%;width:12px;height:12px;animation:1s linear infinite spin;display:inline-block}.progress-toggle{color:#6c757d;font-size:14px;transition:transform .2s}.progress-tracker.expanded .progress-toggle{transform:rotate(180deg)}.progress-error{color:#721c24;background:#f8d7da;border-top:1px solid #f5c6cb;padding:8px 12px;font-size:12px}.progress-content{max-height:0;transition:max-height .3s;overflow:hidden}.progress-tracker.expanded .progress-content{max-height:150px;overflow-y:auto}.progress-content.single-line{max-height:30px;padding:8px 12px;overflow:hidden}.progress-line{color:#6c757d;border-bottom:1px solid #0000000d;padding:4px 12px;line-height:1.3}.progress-line:last-child{border-bottom:none}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.message.ai .progress-tracker,.message.bot .progress-tracker{margin-bottom:8px}@media (width<=768px){.progress-tracker{font-size:12px}.progress-header{padding:6px 10px}.progress-line{padding:3px 10px}.progress-content.single-line{padding:6px 10px}}.chat-input-container{background-color:#fff;border-top:1px solid #e0e0e0;width:100%;padding:10px;font-family:Arial,sans-serif;font-size:14px}.chat-input{align-items:flex-end;gap:10px;display:flex}.input-main{flex:1;position:relative}.message-input{resize:none;border:1px solid #ddd;border-radius:20px;outline:none;width:100%;min-height:40px;padding:10px 40px 10px 15px;font-family:Arial,sans-serif;font-size:14px;transition:border-color .2s}.message-input:focus{border-color:#0084ff}.message-input.over-limit{border-color:#ff4d4f}.character-counter{color:#999;font-size:12px;position:absolute;bottom:10px;right:10px}.character-counter.over-limit{color:#ff4d4f}.input-actions{align-items:center;gap:8px;display:flex}.send-btn{color:#fff;cursor:pointer;background-color:#0084ff;border:none;border-radius:50%;justify-content:center;align-items:center;width:40px;height:40px;transition:background-color .2s;display:flex}.send-btn:hover{background-color:#0077e6}.send-btn:disabled{cursor:not-allowed;background-color:#ccc}.send-btn.form-mode{background-color:#4caf50}.send-btn.form-mode:hover{background-color:#43a047}.loading-spinner{animation:1s linear infinite spin;display:inline-block}.dynamic-form-container{background-color:#f9f9f9;border:1px solid #ddd;border-radius:8px;margin-bottom:10px;padding:15px 15px 5px;font-family:Arial,sans-serif;font-size:14px;position:relative;box-shadow:0 2px 4px #0000000d}.message{width:auto;max-width:90%;margin-bottom:15px}.message.user{margin-left:auto}.message.ai{margin-right:auto}.message-content{width:100%;font-family:Arial,sans-serif;font-size:14px}.form-display{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin:15px 0;padding:15px;font-family:inherit}.form-result-table{border-collapse:collapse;width:100%;font-family:inherit}.form-result-table th{text-align:left;border-bottom:1px solid #e0e0e0;padding:8px;font-family:Arial,sans-serif;font-size:14px;font-weight:600}.form-result-table td{border-bottom:1px solid #f0f0f0;padding:8px;font-family:Arial,sans-serif;font-size:14px}.form-result-table td:first-child{width:35%;font-weight:500}.form-result-table input.form-input,.form-result-table textarea.form-textarea,.form-result-table select.form-select{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:6px;font-family:Arial,sans-serif;font-size:14px}.form-result-table textarea.form-textarea{resize:vertical;min-height:60px}.form-result-table .field-label{vertical-align:top;border-bottom:1px solid #f0f0f0;width:35%;padding:8px;font-weight:500}.form-result-table .field-value{vertical-align:top;border-bottom:1px solid #f0f0f0;padding:8px}.toggle-switch{width:50px;height:24px;display:inline-block;position:relative}.toggle-input{opacity:0;width:0;height:0}.toggle-slider{cursor:pointer;background-color:#ccc;border-radius:24px;transition:all .4s;position:absolute;inset:0}.toggle-knob{content:"";background-color:#fff;border-radius:50%;width:18px;height:18px;transition:all .4s;position:absolute;bottom:3px;left:3px}.material-symbols-outlined{vertical-align:middle;margin-right:8px;font-size:20px}.form-header{border-bottom:1px solid #e0e0e0;align-items:center;padding:8px;display:flex}.message-text{white-space:pre-wrap;word-break:break-word;font-family:Arial,sans-serif;font-size:14px}.form-error{color:red;padding:10px;font-family:Arial,sans-serif;font-size:14px}.dynamic-form-container{background-color:#f9f9f9;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:15px;overflow:hidden}.dynamic-form{padding:15px}.form-header{border-bottom:1px solid #e0e0e0;align-items:center;margin-bottom:15px;padding-bottom:10px;display:flex}.form-icon{color:#555;justify-content:center;align-items:center;width:24px;height:24px;margin-right:10px;display:flex}.form-title{color:#333;font-size:1.2rem;font-weight:600}.form-fields{grid-template-columns:1fr;gap:15px;margin-bottom:20px;display:grid}@media (width>=768px){.form-fields{grid-template-columns:repeat(2,1fr)}}.form-field{margin-bottom:5px}.form-field label{color:#555;margin-bottom:6px;font-size:.9rem;font-weight:500;display:block}.form-field input,.form-field select,.form-field textarea{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem}.form-field input:focus,.form-field select:focus,.form-field textarea:focus{border-color:#4a90e2;outline:none;box-shadow:0 0 0 2px #4a90e233}.form-field textarea{resize:vertical;min-height:80px}.checkbox-container{align-items:center;display:flex}.checkbox-label{cursor:pointer;align-items:center;display:flex}.checkbox-label input[type=checkbox]{width:auto;margin-right:8px}.checkbox-text{color:#555;font-size:.9rem}.field-description{color:#777;margin-top:5px;font-size:.8rem;line-height:1.4;display:block}.form-actions{justify-content:flex-end;gap:10px;margin-top:10px;display:flex}.form-toggle-btn{cursor:pointer;color:#555;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:5px;display:flex}.form-toggle-btn:hover{background-color:#f0f0f0}.form-toggle-btn.active{color:#4a90e2;background-color:#4a90e21a}.required{color:#e53935;margin-left:2px}.form-readonly{padding:10px 0}.form-field-readonly{border-bottom:1px solid #eee;margin-bottom:8px;padding-bottom:8px;display:flex}.field-label{color:#555;flex:0 0 30%;padding-right:10px;font-weight:500}.field-value{word-break:break-word;flex:1}.text-value{white-space:pre-wrap}.message .form-display{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:12px;padding:12px}.message.user .form-display{background-color:#ffffff1a}.message.ai .form-display{background-color:#f5f5fab3}.form-display{background-color:#00000008;border:1px solid #0000001a;border-radius:8px;margin-bottom:10px;padding:12px}.user-form-values{background-color:#007bff0d}.user-form .form-field{margin-bottom:6px!important}.user-form .field-label{color:#555!important;padding:2px 0!important;font-weight:500!important}.user-form .field-value{padding:2px 0!important}.read-only .form-field:hover{background-color:#0000}.dynamic-form.read-only .form-fields{border-top:1px solid #0000000d;margin-top:10px;padding-top:8px}.message-form .form-title{font-size:1em!important}.message-form .form-description{font-size:.85em!important}.form-readonly{width:100%}.form-readonly .field-label{color:#555;font-weight:500}.form-readonly .field-value{word-break:break-word}.form-readonly .text-value{white-space:pre-wrap}.message-text{white-space:pre-wrap;word-break:break-word}.message-content{max-width:100%}.sidebar-language-section{margin-bottom:15px;padding:10px 15px}#language-selector-container{background-color:#ffffff1a;border-radius:5px;flex-direction:column;margin:10px 0;padding:10px;display:flex}#language-selector-container label{color:var(--sidebar-color);margin-bottom:5px;font-size:.9rem;font-weight:500}.language-selector{color:var(--sidebar-color);cursor:pointer;background-color:#0003;border:1px solid #fff3;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem;transition:all .2s}.language-selector:hover{background-color:#0000004d}.language-selector:focus{border-color:var(--primary-color);box-shadow:0 0 0 2px rgba(var(--primary-color-rgb),.3);outline:none}.language-selector option{color:#fff;background-color:#2c3e50;padding:8px}.sidebar-language-section[data-v-f6c05b]{margin-bottom:15px;padding:10px 15px}#language-selector-container[data-v-f6c05b]{background-color:#ffffff1a;border-radius:5px;flex-direction:column;margin:10px 0;padding:10px;display:flex}#language-selector-container label[data-v-f6c05b]{color:var(--sidebar-color);margin-bottom:5px;font-size:.9rem;font-weight:500}.language-selector[data-v-f6c05b]{color:var(--sidebar-color);cursor:pointer;background-color:#0003;border:1px solid #fff3;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem;transition:all .2s}.language-selector[data-v-f6c05b]:hover{background-color:#0000004d}.language-selector[data-v-f6c05b]:focus{border-color:var(--primary-color);box-shadow:0 0 0 2px rgba(var(--primary-color-rgb),.3);outline:none}.language-selector option[data-v-f6c05b]{color:#fff;background-color:#2c3e50;padding:8px}.select-wrapper[data-v-f6c05b]{position:relative}.language-select[data-v-f6c05b]{color:var(--sidebar-color);cursor:pointer;background-color:#0003;border:1px solid #fff3;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem;transition:all .2s}.language-select[data-v-f6c05b]:hover{background-color:#0000004d}.language-select[data-v-f6c05b]:focus{border-color:var(--primary-color);box-shadow:0 0 0 2px rgba(var(--primary-color-rgb),.3);outline:none}.language-select option[data-v-f6c05b]{color:#fff;background-color:#2c3e50;padding:8px}.form-field[data-v-5d4747]{margin-bottom:15px}.form-field input[data-v-5d4747]:focus,.form-field select[data-v-5d4747]:focus,.form-field textarea[data-v-5d4747]:focus{outline:none;box-shadow:0 0 0 2px #4a90e233;border-color:#4a90e2!important}.radio-group[data-v-5d4747]{flex-direction:column;gap:8px;display:flex}.radio-label[data-v-5d4747]{cursor:pointer;color:#555;align-items:center;font-size:.9rem;display:flex}.checkbox-container[data-v-5d4747]{align-items:center;display:flex}.checkbox-label[data-v-5d4747]{cursor:pointer;align-items:center;display:flex}.checkbox-text[data-v-5d4747]{color:#555;font-size:.9rem}.field-description[data-v-5d4747]{color:#777;margin-top:5px;font-size:.8rem;line-height:1.4;display:block}.field-context[data-v-5d4747]{color:#666;background-color:#f8f9fa;border-left:3px solid #4285f4;border-radius:4px;margin-bottom:8px;padding:8px;font-size:.9em}.required[data-v-5d4747]{color:#d93025;margin-left:2px}@media (width<=768px){.form-field[data-v-5d4747]{grid-template-columns:none!important;display:block!important}.form-field label[data-v-5d4747]{margin-bottom:8px;display:block}}.dynamic-form-container[data-v-97c80a]{background-color:#f9f9f9;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:15px;overflow:hidden}.dynamic-form[data-v-97c80a]{padding:15px}.form-header[data-v-97c80a]{border-bottom:1px solid #e0e0e0;align-items:center;margin-bottom:15px;padding-bottom:10px;display:flex}.form-icon[data-v-97c80a]{color:#555;justify-content:center;align-items:center;width:24px;height:24px;margin-right:10px;display:flex}.form-title[data-v-97c80a]{color:#333;font-size:1.2rem;font-weight:600}.form-fields[data-v-97c80a]{grid-template-columns:1fr;gap:15px;margin-bottom:20px;display:grid}@media (width>=768px){.form-fields[data-v-97c80a]{grid-template-columns:repeat(2,1fr)}}.form-field[data-v-97c80a]{margin-bottom:5px}.form-field label[data-v-97c80a]{color:#555;margin-bottom:6px;font-size:.9rem;font-weight:500;display:block}.form-field input[data-v-97c80a],.form-field select[data-v-97c80a],.form-field textarea[data-v-97c80a]{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem}.form-field input[data-v-97c80a]:focus,.form-field select[data-v-97c80a]:focus,.form-field textarea[data-v-97c80a]:focus{border-color:#4a90e2;outline:none;box-shadow:0 0 0 2px #4a90e233}.form-field textarea[data-v-97c80a]{resize:vertical;min-height:80px}.checkbox-container[data-v-97c80a]{align-items:center;display:flex}.checkbox-label[data-v-97c80a]{cursor:pointer;align-items:center;display:flex}.checkbox-label input[type=checkbox][data-v-97c80a]{width:auto;margin-right:8px}.checkbox-text[data-v-97c80a]{color:#555;font-size:.9rem}.field-description[data-v-97c80a]{color:#777;margin-top:5px;font-size:.8rem;line-height:1.4;display:block}.form-actions[data-v-97c80a]{justify-content:flex-end;gap:10px;margin-top:10px;display:flex}.btn[data-v-97c80a]{cursor:pointer;border:none;border-radius:4px;padding:8px 16px;font-size:.9rem;transition:background-color .2s}.btn-primary[data-v-97c80a]{color:#fff;background-color:#4a90e2}.btn-primary[data-v-97c80a]:hover:not(:disabled){background-color:#357abd}.btn-secondary[data-v-97c80a]{color:#fff;background-color:#6c757d}.btn-secondary[data-v-97c80a]:hover:not(:disabled){background-color:#545b62}.btn[data-v-97c80a]:disabled{opacity:.6;cursor:not-allowed}.form-toggle-btn[data-v-97c80a]{cursor:pointer;color:#555;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:5px;display:flex}.form-toggle-btn[data-v-97c80a]:hover{background-color:#f0f0f0}.form-toggle-btn.active[data-v-97c80a]{color:#4a90e2;background-color:#4a90e21a}.required[data-v-97c80a]{color:#e53935;margin-left:2px}.form-readonly[data-v-97c80a]{padding:10px 0}.form-field-readonly[data-v-97c80a]{border-bottom:1px solid #eee;margin-bottom:8px;padding-bottom:8px;display:flex}.field-label[data-v-97c80a]{color:#555;flex:0 0 30%;padding-right:10px;font-weight:500}.field-value[data-v-97c80a]{word-break:break-word;flex:1}.text-value[data-v-97c80a]{white-space:pre-wrap}.chat-input-container[data-v-df2e88]{background-color:#fff;border-top:1px solid #e0e0e0;width:100%;padding:10px;font-family:Arial,sans-serif;font-size:14px}.chat-input[data-v-df2e88]{align-items:flex-end;gap:10px;display:flex}.input-main[data-v-df2e88]{flex:1;position:relative}.message-input[data-v-df2e88]{resize:none;border:1px solid #ddd;border-radius:20px;outline:none;width:100%;min-height:40px;padding:10px 40px 10px 15px;font-family:Arial,sans-serif;font-size:14px;transition:border-color .2s}.message-input[data-v-df2e88]:focus{border-color:#0084ff}.message-input.over-limit[data-v-df2e88]{border-color:#ff4d4f}.character-counter[data-v-df2e88]{color:#999;font-size:12px;position:absolute;bottom:10px;right:10px}.character-counter.over-limit[data-v-df2e88]{color:#ff4d4f}.input-actions[data-v-df2e88]{align-items:center;gap:8px;display:flex}.send-btn[data-v-df2e88]{color:#fff;cursor:pointer;background-color:#0084ff;border:none;border-radius:50%;justify-content:center;align-items:center;width:40px;height:40px;transition:background-color .2s;display:flex}.send-btn[data-v-df2e88]:hover{background-color:#0077e6}.send-btn[data-v-df2e88]:disabled{cursor:not-allowed;background-color:#ccc}.send-btn.form-mode[data-v-df2e88]{background-color:#4caf50}.send-btn.form-mode[data-v-df2e88]:hover{background-color:#43a047}.loading-spinner[data-v-df2e88]{animation:1s linear infinite spin-df2e88;display:inline-block}@keyframes spin-df2e88{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.dynamic-form-container[data-v-df2e88]{background-color:#f9f9f9;border:1px solid #ddd;border-radius:8px;margin-bottom:10px;padding:15px 15px 5px;font-family:Arial,sans-serif;font-size:14px;position:relative;box-shadow:0 2px 4px #0000000d}.progress-tracker[data-v-237cbd]{background-color:#f9f9f9;border:1px solid #e0e0e0;border-radius:8px;margin:10px 0;font-family:Arial,sans-serif;font-size:14px;transition:all .3s}.progress-tracker.expanded[data-v-237cbd]{box-shadow:0 2px 8px #0000001a}.progress-tracker.completed[data-v-237cbd]{background-color:#f1f8e9;border-color:#4caf50}.progress-tracker.error[data-v-237cbd]{background-color:#ffebee;border-color:#f44336}.progress-header[data-v-237cbd]{cursor:pointer;background-color:#fff;border-bottom:1px solid #e0e0e0;border-radius:8px 8px 0 0;justify-content:space-between;align-items:center;padding:12px 15px;transition:background-color .2s;display:flex}.progress-header[data-v-237cbd]:hover{background-color:#f5f5f5}.progress-title[data-v-237cbd]{color:#333;align-items:center;font-weight:500;display:flex}.status-icon[data-v-237cbd]{margin-right:8px;font-size:16px;font-weight:700}.status-icon.completed[data-v-237cbd]{color:#4caf50}.status-icon.error[data-v-237cbd]{color:#f44336}.status-icon.in-progress[data-v-237cbd]{color:#2196f3}.spinner[data-v-237cbd]{border:2px solid #f3f3f3;border-top-color:#2196f3;border-radius:50%;width:16px;height:16px;margin-right:8px;animation:1s linear infinite spin-237cbd;display:inline-block}@keyframes spin-237cbd{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.progress-toggle[data-v-237cbd]{color:#666;font-size:12px;transition:transform .2s}.progress-tracker.expanded .progress-toggle[data-v-237cbd]{transform:rotate(180deg)}.progress-error[data-v-237cbd]{color:#c62828;background-color:#ffcdd2;border-top:1px solid #e0e0e0;padding:10px 15px;font-size:13px}.progress-content[data-v-237cbd]{background-color:#fff;border-radius:0 0 8px 8px;max-height:200px;padding:10px 15px;transition:max-height .3s;overflow-y:auto}.progress-content.single-line[data-v-237cbd]{max-height:40px;overflow:hidden}.progress-line[data-v-237cbd]{color:#555;word-break:break-word;padding:2px 0;font-size:13px;line-height:1.4}.progress-line[data-v-237cbd]:last-child{color:#333;font-weight:500}.progress-content[data-v-237cbd]::-webkit-scrollbar{width:4px}.progress-content[data-v-237cbd]::-webkit-scrollbar-track{background:#f1f1f1;border-radius:2px}.progress-content[data-v-237cbd]::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:2px}.progress-content[data-v-237cbd]::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (width<=768px){.progress-header[data-v-237cbd]{padding:10px 12px}.progress-content[data-v-237cbd]{max-height:150px;padding:8px 12px}.progress-content.single-line[data-v-237cbd]{max-height:35px}.progress-line[data-v-237cbd]{font-size:12px}}.progress-line[data-v-237cbd]{animation:.3s ease-in fadeIn-237cbd}@keyframes fadeIn-237cbd{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}.message[data-v-a78b4e]{width:auto;max-width:90%;margin-bottom:15px}.message.user[data-v-a78b4e]{margin-left:auto}.message.ai[data-v-a78b4e]{margin-right:auto}.message-content[data-v-a78b4e]{width:100%;font-family:Arial,sans-serif;font-size:14px}.form-display[data-v-a78b4e]{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin:15px 0;padding:15px;font-family:inherit}.form-result-table[data-v-a78b4e]{border-collapse:collapse;width:100%;font-family:inherit}.form-result-table th[data-v-a78b4e]{text-align:left;border-bottom:1px solid #e0e0e0;padding:8px;font-family:Arial,sans-serif;font-size:14px;font-weight:600}.form-result-table td[data-v-a78b4e]{border-bottom:1px solid #f0f0f0;padding:8px;font-family:Arial,sans-serif;font-size:14px}.form-result-table td[data-v-a78b4e]:first-child{width:35%;font-weight:500}.form-result-table input.form-input[data-v-a78b4e],.form-result-table textarea.form-textarea[data-v-a78b4e],.form-result-table select.form-select[data-v-a78b4e]{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:6px;font-family:Arial,sans-serif;font-size:14px}.form-result-table textarea.form-textarea[data-v-a78b4e]{resize:vertical;min-height:60px}.form-result-table .field-label[data-v-a78b4e]{vertical-align:top;border-bottom:1px solid #f0f0f0;width:35%;padding:8px;font-weight:500}.form-result-table .field-value[data-v-a78b4e]{vertical-align:top;border-bottom:1px solid #f0f0f0;padding:8px}.toggle-switch[data-v-a78b4e]{width:50px;height:24px;display:inline-block;position:relative}.toggle-input[data-v-a78b4e]{opacity:0;width:0;height:0}.toggle-slider[data-v-a78b4e]{cursor:pointer;background-color:#ccc;border-radius:24px;transition:all .4s;position:absolute;inset:0}.toggle-knob[data-v-a78b4e]{content:"";background-color:#fff;border-radius:50%;width:18px;height:18px;transition:all .4s;position:absolute;bottom:3px;left:3px}.material-symbols-outlined[data-v-a78b4e]{vertical-align:middle;margin-right:8px;font-size:20px}.form-header[data-v-a78b4e]{border-bottom:1px solid #e0e0e0;align-items:center;padding:8px;display:flex}.message-text[data-v-a78b4e]{white-space:pre-wrap;word-break:break-word;font-family:Arial,sans-serif;font-size:14px}.form-error[data-v-a78b4e]{color:red;padding:10px;font-family:Arial,sans-serif;font-size:14px}.error-content[data-v-a78b4e]{background-color:#ffebee;border:1px solid #f44336;border-radius:4px;padding:10px}.retry-btn[data-v-a78b4e]{color:#fff;cursor:pointer;background-color:#f44336;border:none;border-radius:4px;margin-top:10px;padding:8px 16px;font-size:14px}.retry-btn[data-v-a78b4e]:hover{background-color:#d32f2f}@media (width<=768px){.message[data-v-a78b4e]{max-width:95%}.form-result-table td[data-v-a78b4e]:first-child{width:40%}}.typing-indicator[data-v-a78025]{background-color:#f0f0f0;border-radius:18px;align-items:center;width:fit-content;max-width:80px;margin:10px 0;padding:10px 15px;display:flex}.typing-dot[data-v-a78025]{background-color:#999;border-radius:50%;width:8px;height:8px;margin:0 2px;animation:1.4s ease-in-out infinite typing-bounce-a78025}.typing-dot[data-v-a78025]:first-child{animation-delay:-.32s}.typing-dot[data-v-a78025]:nth-child(2){animation-delay:-.16s}.typing-dot[data-v-a78025]:nth-child(3){animation-delay:0s}.typing-text[data-v-a78025]{color:#666;margin-left:10px;font-size:.9rem;font-style:italic}@keyframes typing-bounce-a78025{0%,80%,to{opacity:.5;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}@keyframes typing-pulse-a78025{0%,60%,to{transform:initial;opacity:.4}30%{opacity:1;transform:scale(1.2)}}@media (width<=768px){.typing-indicator[data-v-a78025]{margin:8px 0;padding:8px 12px}.typing-dot[data-v-a78025]{width:6px;height:6px}.typing-text[data-v-a78025]{margin-left:8px;font-size:.8rem}}@media (prefers-color-scheme:dark){.typing-indicator[data-v-a78025]{background-color:#2a2a2a}.typing-dot[data-v-a78025]{background-color:#ccc}.typing-text[data-v-a78025]{color:#aaa}}@media (prefers-contrast:high){.typing-indicator[data-v-a78025]{border:1px solid #333}.typing-dot[data-v-a78025]{background-color:#000}.typing-text[data-v-a78025]{color:#000}}@media (prefers-reduced-motion:reduce){.typing-dot[data-v-a78025]{opacity:.7;animation:none}.typing-indicator[data-v-a78025]{opacity:.8}}.message-history-container[data-v-efbfed]{flex-direction:column;height:100%;display:flex;overflow:hidden}.chat-messages[data-v-efbfed]{scroll-behavior:smooth;flex:1;padding:10px;overflow-y:auto}.load-more-indicator[data-v-efbfed]{text-align:center;color:#666;padding:10px;font-size:.9rem}.empty-state[data-v-efbfed]{text-align:center;color:#666;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:40px 20px;display:flex}.empty-icon[data-v-efbfed]{opacity:.5;margin-bottom:16px;font-size:3rem}.empty-text[data-v-efbfed]{color:#333;margin-bottom:8px;font-size:1.2rem;font-weight:500}.empty-subtext[data-v-efbfed]{color:#666;max-width:300px;font-size:.9rem;line-height:1.4}.chat-messages[data-v-efbfed]::-webkit-scrollbar{width:6px}.chat-messages[data-v-efbfed]::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.chat-messages[data-v-efbfed]::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px}.chat-messages[data-v-efbfed]::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (width<=768px){.chat-messages[data-v-efbfed]{padding:8px}.empty-state[data-v-efbfed]{padding:20px 16px}.empty-icon[data-v-efbfed]{font-size:2.5rem}.empty-text[data-v-efbfed]{font-size:1.1rem}}.form-message[data-v-199543]{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:12px;padding:12px}.form-message-header[data-v-199543]{border-bottom:1px solid #e0e0e0;align-items:center;margin-bottom:10px;padding-bottom:8px;display:flex}.form-message-icon[data-v-199543]{color:#555;margin-right:8px;font-size:18px}.form-message-title[data-v-199543]{color:#333;font-size:1em;font-weight:600}.form-message-fields[data-v-199543]{flex-direction:column;gap:8px;display:flex}.form-message-field[data-v-199543]{border-bottom:1px solid #0000000d;justify-content:space-between;align-items:flex-start;padding:4px 0;display:flex}.form-message-field[data-v-199543]:last-child{border-bottom:none}.field-message-label[data-v-199543]{color:#555;word-break:break-word;flex:0 0 40%;padding-right:10px;font-weight:500}.field-message-value[data-v-199543]{color:#333;word-break:break-word;text-align:right;flex:1}.field-message-value.text-value[data-v-199543]{white-space:pre-wrap;text-align:left}.message.user .form-message[data-v-199543]{background-color:#ffffff1a}.message.ai .form-message[data-v-199543]{background-color:#f5f5fab3}.form-display[data-v-199543]{background-color:#00000008;border:1px solid #0000001a;border-radius:8px;margin-bottom:10px;padding:12px}.user-form-values[data-v-199543]{background-color:#007bff0d}.user-form .form-field[data-v-199543]{margin-bottom:6px!important}.user-form .field-label[data-v-199543]{color:#555!important;padding:2px 0!important;font-weight:500!important}.user-form .field-value[data-v-199543]{padding:2px 0!important}.read-only .form-field[data-v-199543]:hover{background-color:#0000}.dynamic-form.read-only .form-fields[data-v-199543]{border-top:1px solid #0000000d;margin-top:10px;padding-top:8px}.message-form .form-title[data-v-199543]{font-size:1em!important}.message-form .form-description[data-v-199543]{font-size:.85em!important}.form-readonly[data-v-199543]{width:100%}.form-readonly .field-label[data-v-199543]{color:#555;font-weight:500}.form-readonly .field-value[data-v-199543]{word-break:break-word}.form-readonly .text-value[data-v-199543]{white-space:pre-wrap}.message-text[data-v-199543]{white-space:pre-wrap;word-break:break-word}.message-content[data-v-199543]{max-width:100%}@media (width<=768px){.form-message-field[data-v-199543]{flex-direction:column;align-items:flex-start;gap:4px}.field-message-label[data-v-199543]{flex:none;padding-right:0}.field-message-value[data-v-199543]{text-align:left}.form-message[data-v-199543]{padding:10px}.form-message-title[data-v-199543]{font-size:.9em}}@media (prefers-color-scheme:dark){.form-message[data-v-199543]{background-color:#282828b3;border-color:#555}.form-message-header[data-v-199543]{border-bottom-color:#555}.form-message-title[data-v-199543]{color:#e0e0e0}.field-message-label[data-v-199543]{color:#ccc}.field-message-value[data-v-199543]{color:#e0e0e0}.form-message-field[data-v-199543]{border-bottom-color:#ffffff1a}}@media (prefers-contrast:high){.form-message[data-v-199543]{background-color:#fff;border:2px solid #000}.form-message-title[data-v-199543],.field-message-label[data-v-199543],.field-message-value[data-v-199543]{color:#000}.form-message-header[data-v-199543]{border-bottom:2px solid #000}}.chat-app-container[data-v-bfe173]{flex-direction:column;width:100%;height:100vh;display:flex}.chat-messages-area[data-v-bfe173]{flex:1;overflow:hidden}.chat-input-area[data-v-bfe173]{flex-shrink:0}@media (width<=768px){.chat-app-container[data-v-bfe173]{height:100vh}} \ No newline at end of file +:root{--primary-color:#007bff;--secondary-color:#6c757d;--background-color:#fff;--text-color:#212529;--sidebar-color:#f8f9fa;--message-user-bg:#e9f5ff;--message-bot-bg:#f8f9fa;--border-radius:8px;--spacing:16px}.app-container{width:100%;height:100vh;display:flex}.sidebar{background-color:var(--sidebar-background);flex-direction:column;width:300px;padding:20px;display:flex;overflow-y:auto}.sidebar-logo{text-align:center;margin-bottom:20px}.sidebar-logo img{max-width:100%;max-height:100px}.sidebar-make-name{text-align:center;margin-bottom:20px;font-size:24px;font-weight:700}.sidebar-explanation{background-color:var(--markdown-background-color);color:var(--markdown-text-color);border-radius:5px;margin-top:20px;padding:10px;overflow-y:auto}.sidebar-explanation *{color:inherit}.sidebar-explanation a{color:var(--primary-color);text-decoration:underline}.sidebar-explanation ul,.sidebar-explanation ol{margin:10px 0;padding-left:20px}.sidebar-explanation li{margin-bottom:5px}.sidebar-explanation ul li{list-style-type:disc}.sidebar-explanation ol li{list-style-type:decimal}.content-area{background:linear-gradient(135deg,var(--gradient-start-color),var(--gradient-end-color));flex-direction:column;flex:1;display:flex;overflow-y:auto}body{color:var(--text-color);background-color:var(--background-color);height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;line-height:1.6;overflow:hidden}.container{width:100%;height:100vh}.chat-container{flex-direction:column;flex:1;height:100%;min-height:0;display:flex}.sidebar{background-color:var(--sidebar-background);width:280px;padding:var(--spacing);border-right:1px solid #0000001a;flex-direction:column;display:flex;overflow-y:auto}.logo{margin-bottom:var(--spacing);text-align:center}.logo img{max-width:100%;max-height:60px}.sidebar-content{flex-direction:column;flex:1;display:flex}.sidebar-text{margin-bottom:var(--spacing)}.team-info{padding-top:var(--spacing);border-top:1px solid #0000001a;margin-top:auto}.team-member{align-items:center;margin-bottom:8px;display:flex}.team-member img{border-radius:50%;width:32px;height:32px;margin-right:8px}.chat-main{flex-direction:column;flex:1;height:100%;display:flex}.chat-header{padding:var(--spacing);border-bottom:1px solid #0000001a}.language-change-indicator{background-color:rgba(var(--primary-color-rgb,0,123,255),.2);color:#fff;text-align:center;border-radius:4px;margin-bottom:10px;padding:5px 8px;font-size:.9em;animation:3s ease-in-out fadeInOut}@keyframes fadeInOut{0%{opacity:0}10%{opacity:1}90%{opacity:1}to{opacity:0}}.language-change-indicator.success{color:#155724;background:#d4edda}.language-change-indicator.error{color:#721c24;background:#f8d7da}.user-message{float:right}.bot-message{float:left}.user-message .message-content{background-color:var(--message-user-bg);color:var(--text-color)}.bot-message .message-content{background-color:var(--message-bot-bg);color:var(--text-color)}#chat-input{border-radius:var(--border-radius);resize:none;border:1px solid #0003;flex:1;height:60px;margin-right:8px;padding:12px}.error-container{justify-content:center;align-items:center;height:100vh;display:flex}.error-box{border-radius:var(--border-radius);text-align:center;background-color:#fff;max-width:500px;padding:2rem;box-shadow:0 4px 6px #0000001a}.error-message{color:#dc3545;margin:1rem 0}.error-actions{margin-top:1.5rem}.chat-app-container{box-sizing:border-box;flex-direction:column;width:100%;height:100%;min-height:0;padding:20px;display:flex}.chat-component-container{flex-direction:column;flex:1;width:100%;max-width:1000px;margin-left:auto;margin-right:auto;display:flex}.chat-messages-area{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);background:#ffffff1a;border-radius:15px;flex-direction:column;flex:1;align-self:center;width:100%;max-width:1000px;min-height:0;margin-bottom:20px;margin-left:auto;margin-right:auto;display:flex;overflow:hidden;box-shadow:0 4px 20px #0000001a}.chat-input-area{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:10;background:#ffffff26;border:1px solid #fff3;border-radius:15px;flex:none;align-self:center;width:100%;max-width:1000px;margin-left:auto;margin-right:auto;box-shadow:0 4px 20px #0000001a}.message-history-container{box-sizing:border-box;flex-direction:column;width:100%;max-width:1000px;height:100%;min-height:0;margin-left:auto;margin-right:auto;padding:20px;display:flex}.chat-messages{scroll-behavior:smooth;flex:1;margin-right:-10px;padding-right:10px;overflow-y:auto}.chat-input-container{box-sizing:border-box;width:100%;max-width:1000px;margin-left:auto;margin-right:auto;padding:20px;position:relative}.chat-input{background:#fff;border:1px solid #0000000d;border-radius:15px;align-items:flex-end;gap:12px;padding:20px;display:flex;box-shadow:0 2px 15px #0000001a}.message-input{resize:none;box-sizing:border-box;border:1px solid #ddd;border-radius:25px;outline:none;width:100%;min-height:45px;max-height:120px;padding:12px 18px;font-family:inherit;font-size:14px;line-height:1.4;transition:all .2s}.message-input:focus{border-color:var(--primary-color);box-shadow:0 0 0 3px #007bff1a}.message-input.over-limit{background-color:#dc35450d;border-color:#dc3545}.send-btn{background:var(--primary-color);color:#fff;cursor:pointer;border:none;border-radius:50%;justify-content:center;align-items:center;width:45px;height:45px;font-size:18px;transition:all .2s;display:flex;box-shadow:0 2px 10px #0000001a}.send-btn:hover:not(:disabled){background:var(--secondary-color);transform:scale(1.05);box-shadow:0 4px 15px #0003}.send-btn:disabled{cursor:not-allowed;box-shadow:none;background:#ccc;transform:none}.character-counter{color:#666;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);background:#ffffffe6;border-radius:10px;padding:2px 6px;font-size:12px;position:absolute;bottom:-25px;right:15px}.character-counter.over-limit{color:#dc3545;background:#dc35451a;font-weight:700}.loading-spinner{font-size:16px;animation:1s linear infinite spin}@media (width<=768px){.chat-app-container{padding:10px}.chat-messages-area{max-width:100%;margin-bottom:15px}.chat-input-area{max-width:100%}.message-history-container,.chat-input-container{max-width:100%;padding:15px}.chat-input{gap:10px;padding:15px}.action-btn{width:40px;height:40px;font-size:16px}.message-input{min-height:40px;padding:10px 15px;font-size:16px}.chat-component-container{max-width:100%}}@media (width<=480px){.chat-app-container{padding:8px}.chat-messages-area{margin-bottom:12px}.message-history-container,.chat-input-container{padding:12px}}.chat-input.loading .message-input{opacity:.7}.chat-input.loading .action-btn{animation:1.5s infinite pulse}@keyframes pulse{0%{opacity:1}50%{opacity:.5}to{opacity:1}}.chat-messages::-webkit-scrollbar{width:6px}.chat-messages::-webkit-scrollbar-track{background:#0000001a;border-radius:3px}.chat-messages::-webkit-scrollbar-thumb{background:#0000004d;border-radius:3px}.chat-messages::-webkit-scrollbar-thumb:hover{background:#00000080}.message-text:empty{display:none}.progress-tracker .status-icon.error{color:#f44336}.progress-tracker.error .progress-header{background-color:#f443361a;border-color:#f44336}.message-content:has(.message-text:empty) .message-progress{margin-bottom:0}.message-content:has(.message-text:empty):not(:has(.message-progress.completed)):not(:has(.message-progress.error)){box-shadow:none;background:0 0;border:none;margin:0;padding:0}.chat-input:focus-within{border-color:#007bff4d;box-shadow:0 2px 20px #007bff33}.chat-messages-area,.chat-input-area{transition:all .3s}.chat-messages-area:hover,.chat-input-area:hover{box-shadow:0 6px 25px #00000026}.message{clear:both;margin-bottom:16px;padding:0 20px;animation:.3s ease-out messageSlideIn;display:flex}.message.user{justify-content:flex-end}.message.ai,.message.bot{justify-content:flex-start}.message-content{word-wrap:break-word;border-radius:18px;max-width:70%;padding:12px 16px;transition:all .2s;display:inline-block;position:relative;box-shadow:0 2px 8px #0000001a}.message.user .message-content{color:#fff;background:#0000001a;border-bottom-right-radius:4px}.message.ai .message-content,.message.bot .message-content{color:#212529;background:#ffffff1a;border-bottom-left-radius:4px;margin-right:60px}.message-text{margin-bottom:6px;font-size:14px;line-height:1.4}.message-text p{margin:0}.message-text p+p{margin-top:8px}.btn-small{cursor:pointer;border:none;border-radius:4px;padding:4px 12px;font-size:12px;transition:all .2s}.btn-primary{color:#fff;background:#007bff}.btn-primary:hover{background:#0056b3}.btn-secondary{color:#fff;background:#6c757d}.btn-secondary:hover{background:#545b62}.form-message{justify-content:center;margin:20px 0}.form-message .message-content{background:#fff;border:1px solid #e9ecef;border-radius:12px;max-width:90%;padding:20px;box-shadow:0 4px 12px #0000001a}.system-message{text-align:center;color:#6c757d;background:#6c757d1a;border-radius:20px;justify-content:center;align-items:center;gap:8px;max-width:80%;margin:10px auto;padding:8px 16px;font-size:13px;display:flex}.system-icon{font-size:14px}.error-message{color:#721c24;background:#f8d7da;border:1px solid #f5c6cb;border-radius:8px;justify-content:space-between;align-items:center;gap:12px;max-width:80%;margin:10px auto;padding:12px 16px;display:flex}.error-icon{color:#dc3545;font-size:16px}.retry-btn{color:#fff;cursor:pointer;background:#dc3545;border:none;border-radius:4px;padding:4px 12px;font-size:12px;transition:background-color .2s}.retry-btn:hover{background:#c82333}.message-reactions{flex-wrap:wrap;gap:4px;margin-top:8px;display:flex}.reaction{cursor:pointer;background:#0000000d;border:1px solid #0000001a;border-radius:12px;padding:2px 8px;font-size:12px;transition:all .2s}.reaction:hover{background:#0000001a;transform:scale(1.05)}.message-image{cursor:pointer;border-radius:8px;max-width:100%;max-height:300px;margin-bottom:8px;transition:transform .2s}.message-image:hover{transform:scale(1.02)}.image-caption{opacity:.9;margin-bottom:6px;font-size:13px}.file-attachment{background:#00000008;border-radius:8px;align-items:center;gap:12px;margin-bottom:8px;padding:12px;display:flex}.file-icon{font-size:24px}.file-info{flex:1}.file-name{margin-bottom:2px;font-weight:500}.file-size{opacity:.7;font-size:12px}.file-download{cursor:pointer;font-size:20px;text-decoration:none;transition:transform .2s}.file-download:hover{transform:scale(1.1)}.message-content:hover{transform:translateY(-1px);box-shadow:0 4px 12px #00000026}@keyframes messageSlideIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.empty-state{text-align:center;color:#6c757d;padding:40px 20px}.empty-icon{opacity:.5;margin-bottom:16px;font-size:48px}.empty-text{margin-bottom:8px;font-size:18px;font-weight:500}.empty-subtext{opacity:.8;font-size:14px}@media (width<=768px){.message{padding:0 15px}.message-content{max-width:85%;padding:10px 14px;font-size:14px}.message.user .message-content{margin-left:40px}.message.ai .message-content,.message.bot .message-content{margin-right:40px}}@media (width<=480px){.message{padding:0 10px}.message-content{max-width:90%;margin-left:20px!important;margin-right:20px!important}}.progress-tracker{background:#f8f9fa;border-radius:8px;margin:8px 0;font-size:13px;transition:all .3s;overflow:hidden}.progress-tracker.expanded{max-height:200px}.progress-tracker.completed{background:#9bff9b1a}.progress-header{cursor:pointer;background:#00000005;border-bottom:1px solid #0000;justify-content:space-between;align-items:center;padding:8px 12px;transition:all .2s;display:flex}.progress-header:hover{background:#0000000d}.progress-tracker.expanded .progress-header{border-bottom-color:#e9ecef}.progress-title{color:#495057;align-items:center;gap:8px;font-weight:500;display:flex}.status-icon{border-radius:50%;width:12px;height:12px;display:inline-block;position:relative}.status-icon.completed{color:#fff;text-align:center;background:#28a745;font-size:8px;line-height:12px}.status-icon.in-progress{background:#007bff;animation:1.5s infinite pulse}.spinner{border:2px solid #f3f3f3;border-top-color:#007bff;border-radius:50%;width:12px;height:12px;animation:1s linear infinite spin;display:inline-block}.progress-toggle{color:#6c757d;font-size:14px;transition:transform .2s}.progress-tracker.expanded .progress-toggle{transform:rotate(180deg)}.progress-error{color:#721c24;background:#f8d7da;border-top:1px solid #f5c6cb;padding:8px 12px;font-size:12px}.progress-content{max-height:0;transition:max-height .3s;overflow:hidden}.progress-tracker.expanded .progress-content{max-height:150px;overflow-y:auto}.progress-content.single-line{max-height:30px;padding:8px 12px;overflow:hidden}.progress-line{color:#6c757d;border-bottom:1px solid #0000000d;padding:4px 12px;line-height:1.3}.progress-line:last-child{border-bottom:none}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.message.ai .progress-tracker,.message.bot .progress-tracker{margin-bottom:8px}@media (width<=768px){.progress-tracker{font-size:12px}.progress-header{padding:6px 10px}.progress-line{padding:3px 10px}.progress-content.single-line{padding:6px 10px}}.chat-input-container{background-color:#fff;border-top:1px solid #e0e0e0;width:100%;padding:10px;font-family:Arial,sans-serif;font-size:14px}.chat-input{align-items:flex-end;gap:10px;display:flex}.input-main{flex:1;position:relative}.message-input{resize:none;border:1px solid #ddd;border-radius:20px;outline:none;width:100%;min-height:40px;padding:10px 40px 10px 15px;font-family:Arial,sans-serif;font-size:14px;transition:border-color .2s}.message-input:focus{border-color:#0084ff}.message-input.over-limit{border-color:#ff4d4f}.character-counter{color:#999;font-size:12px;position:absolute;bottom:10px;right:10px}.character-counter.over-limit{color:#ff4d4f}.input-actions{align-items:center;gap:8px;display:flex}.send-btn{color:#fff;cursor:pointer;background-color:#0084ff;border:none;border-radius:50%;justify-content:center;align-items:center;width:40px;height:40px;transition:background-color .2s;display:flex}.send-btn:hover{background-color:#0077e6}.send-btn:disabled{cursor:not-allowed;background-color:#ccc}.send-btn.form-mode{background-color:#4caf50}.send-btn.form-mode:hover{background-color:#43a047}.loading-spinner{animation:1s linear infinite spin;display:inline-block}.dynamic-form-container{background-color:#f9f9f9;border:1px solid #ddd;border-radius:8px;margin-bottom:10px;padding:15px 15px 5px;font-family:Arial,sans-serif;font-size:14px;position:relative;box-shadow:0 2px 4px #0000000d}.message{width:auto;max-width:90%;margin-bottom:15px}.message.user{margin-left:auto}.message.ai{margin-right:auto}.message-content{width:100%;font-family:Arial,sans-serif;font-size:14px}.form-display{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin:15px 0;padding:15px;font-family:inherit}.form-result-table{border-collapse:collapse;width:100%;font-family:inherit}.form-result-table th{text-align:left;border-bottom:1px solid #e0e0e0;padding:8px;font-family:Arial,sans-serif;font-size:14px;font-weight:600}.form-result-table td{border-bottom:1px solid #f0f0f0;padding:8px;font-family:Arial,sans-serif;font-size:14px}.form-result-table td:first-child{width:35%;font-weight:500}.form-result-table input.form-input,.form-result-table textarea.form-textarea,.form-result-table select.form-select{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:6px;font-family:Arial,sans-serif;font-size:14px}.form-result-table textarea.form-textarea{resize:vertical;min-height:60px}.form-result-table .field-label{vertical-align:top;border-bottom:1px solid #f0f0f0;width:35%;padding:8px;font-weight:500}.form-result-table .field-value{vertical-align:top;border-bottom:1px solid #f0f0f0;padding:8px}.toggle-switch{width:50px;height:24px;display:inline-block;position:relative}.toggle-input{opacity:0;width:0;height:0}.toggle-slider{cursor:pointer;background-color:#ccc;border-radius:24px;transition:all .4s;position:absolute;inset:0}.toggle-knob{content:"";background-color:#fff;border-radius:50%;width:18px;height:18px;transition:all .4s;position:absolute;bottom:3px;left:3px}.material-symbols-outlined{vertical-align:middle;margin-right:8px;font-size:20px}.form-header{border-bottom:1px solid #e0e0e0;align-items:center;padding:8px;display:flex}.message-text{white-space:pre-wrap;word-break:break-word;font-family:Arial,sans-serif;font-size:14px}.form-error{color:red;padding:10px;font-family:Arial,sans-serif;font-size:14px}.dynamic-form-container{background-color:#f9f9f9;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:15px;overflow:hidden}.dynamic-form{padding:15px}.form-header{border-bottom:1px solid #e0e0e0;align-items:center;margin-bottom:15px;padding-bottom:10px;display:flex}.form-icon{color:#555;justify-content:center;align-items:center;width:24px;height:24px;margin-right:10px;display:flex}.form-title{color:#333;font-size:1.2rem;font-weight:600}.form-fields{grid-template-columns:1fr;gap:15px;margin-bottom:20px;display:grid}@media (width>=768px){.form-fields{grid-template-columns:repeat(2,1fr)}}.form-field{margin-bottom:5px}.form-field label{color:#555;margin-bottom:6px;font-size:.9rem;font-weight:500;display:block}.form-field input,.form-field select,.form-field textarea{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem}.form-field input:focus,.form-field select:focus,.form-field textarea:focus{border-color:#4a90e2;outline:none;box-shadow:0 0 0 2px #4a90e233}.form-field textarea{resize:vertical;min-height:80px}.checkbox-container{align-items:center;display:flex}.checkbox-label{cursor:pointer;align-items:center;display:flex}.checkbox-label input[type=checkbox]{width:auto;margin-right:8px}.checkbox-text{color:#555;font-size:.9rem}.field-description{color:#777;margin-top:5px;font-size:.8rem;line-height:1.4;display:block}.form-actions{justify-content:flex-end;gap:10px;margin-top:10px;display:flex}.form-toggle-btn{cursor:pointer;color:#555;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:5px;display:flex}.form-toggle-btn:hover{background-color:#f0f0f0}.form-toggle-btn.active{color:#4a90e2;background-color:#4a90e21a}.required{color:#e53935;margin-left:2px}.form-readonly{padding:10px 0}.form-field-readonly{border-bottom:1px solid #eee;margin-bottom:8px;padding-bottom:8px;display:flex}.field-label{color:#555;flex:0 0 30%;padding-right:10px;font-weight:500}.field-value{word-break:break-word;flex:1}.text-value{white-space:pre-wrap}.message .form-display{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:12px;padding:12px}.message.user .form-display{background-color:#ffffff1a}.message.ai .form-display{background-color:#f5f5fab3}.form-display{background-color:#00000008;border:1px solid #0000001a;border-radius:8px;margin-bottom:10px;padding:12px}.user-form-values{background-color:#007bff0d}.user-form .form-field{margin-bottom:6px!important}.user-form .field-label{color:#555!important;padding:2px 0!important;font-weight:500!important}.user-form .field-value{padding:2px 0!important}.read-only .form-field:hover{background-color:#0000}.dynamic-form.read-only .form-fields{border-top:1px solid #0000000d;margin-top:10px;padding-top:8px}.message-form .form-title{font-size:1em!important}.message-form .form-description{font-size:.85em!important}.form-readonly{width:100%}.form-readonly .field-label{color:#555;font-weight:500}.form-readonly .field-value{word-break:break-word}.form-readonly .text-value{white-space:pre-wrap}.message-text{white-space:pre-wrap;word-break:break-word}.message-content{max-width:100%}.sidebar-language-section{margin-bottom:15px;padding:10px 15px}#language-selector-container{background-color:#ffffff1a;border-radius:5px;flex-direction:column;margin:10px 0;padding:10px;display:flex}#language-selector-container label{color:var(--sidebar-color);margin-bottom:5px;font-size:.9rem;font-weight:500}.language-selector{color:var(--sidebar-color);cursor:pointer;background-color:#0003;border:1px solid #fff3;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem;transition:all .2s}.language-selector:hover{background-color:#0000004d}.language-selector:focus{border-color:var(--primary-color);box-shadow:0 0 0 2px rgba(var(--primary-color-rgb),.3);outline:none}.language-selector option{color:#fff;background-color:#2c3e50;padding:8px}.sidebar-language-section[data-v-f6c05b]{margin-bottom:15px;padding:10px 15px}#language-selector-container[data-v-f6c05b]{background-color:#ffffff1a;border-radius:5px;flex-direction:column;margin:10px 0;padding:10px;display:flex}#language-selector-container label[data-v-f6c05b]{color:var(--sidebar-color);margin-bottom:5px;font-size:.9rem;font-weight:500}.language-selector[data-v-f6c05b]{color:var(--sidebar-color);cursor:pointer;background-color:#0003;border:1px solid #fff3;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem;transition:all .2s}.language-selector[data-v-f6c05b]:hover{background-color:#0000004d}.language-selector[data-v-f6c05b]:focus{border-color:var(--primary-color);box-shadow:0 0 0 2px rgba(var(--primary-color-rgb),.3);outline:none}.language-selector option[data-v-f6c05b]{color:#fff;background-color:#2c3e50;padding:8px}.select-wrapper[data-v-f6c05b]{position:relative}.language-select[data-v-f6c05b]{color:var(--sidebar-color);cursor:pointer;background-color:#0003;border:1px solid #fff3;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem;transition:all .2s}.language-select[data-v-f6c05b]:hover{background-color:#0000004d}.language-select[data-v-f6c05b]:focus{border-color:var(--primary-color);box-shadow:0 0 0 2px rgba(var(--primary-color-rgb),.3);outline:none}.language-select option[data-v-f6c05b]{color:#fff;background-color:#2c3e50;padding:8px}.form-field[data-v-5d4747]{margin-bottom:15px}.form-field input[data-v-5d4747]:focus,.form-field select[data-v-5d4747]:focus,.form-field textarea[data-v-5d4747]:focus{outline:none;box-shadow:0 0 0 2px #4a90e233;border-color:#4a90e2!important}.radio-group[data-v-5d4747]{flex-direction:column;gap:8px;display:flex}.radio-label[data-v-5d4747]{cursor:pointer;color:#555;align-items:center;font-size:.9rem;display:flex}.checkbox-container[data-v-5d4747]{align-items:center;display:flex}.checkbox-label[data-v-5d4747]{cursor:pointer;align-items:center;display:flex}.checkbox-text[data-v-5d4747]{color:#555;font-size:.9rem}.field-description[data-v-5d4747]{color:#777;margin-top:5px;font-size:.8rem;line-height:1.4;display:block}.field-context[data-v-5d4747]{color:#666;background-color:#f8f9fa;border-left:3px solid #4285f4;border-radius:4px;margin-bottom:8px;padding:8px;font-size:.9em}.required[data-v-5d4747]{color:#d93025;margin-left:2px}@media (width<=768px){.form-field[data-v-5d4747]{grid-template-columns:none!important;display:block!important}.form-field label[data-v-5d4747]{margin-bottom:8px;display:block}}.dynamic-form-container[data-v-56e1ea]{background-color:#f9f9f9;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:15px;overflow:hidden}.dynamic-form[data-v-56e1ea]{padding:15px}.form-header[data-v-56e1ea]{border-bottom:1px solid #e0e0e0;align-items:center;margin-bottom:15px;padding-bottom:10px;display:flex}.form-icon[data-v-56e1ea]{color:#555;justify-content:center;align-items:center;width:24px;height:24px;margin-right:10px;display:flex}.form-title[data-v-56e1ea]{color:#333;font-size:1.2rem;font-weight:600}.form-fields[data-v-56e1ea]{grid-template-columns:1fr;gap:15px;margin-bottom:20px;display:grid}@media (width>=768px){.form-fields[data-v-56e1ea]{grid-template-columns:repeat(2,1fr)}}.form-field[data-v-56e1ea]{margin-bottom:5px}.form-field label[data-v-56e1ea]{color:#555;margin-bottom:6px;font-size:.9rem;font-weight:500;display:block}.form-field input[data-v-56e1ea],.form-field select[data-v-56e1ea],.form-field textarea[data-v-56e1ea]{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:8px 12px;font-size:.9rem}.form-field input[data-v-56e1ea]:focus,.form-field select[data-v-56e1ea]:focus,.form-field textarea[data-v-56e1ea]:focus{border-color:#4a90e2;outline:none;box-shadow:0 0 0 2px #4a90e233}.form-field textarea[data-v-56e1ea]{resize:vertical;min-height:80px}.checkbox-container[data-v-56e1ea]{align-items:center;display:flex}.checkbox-label[data-v-56e1ea]{cursor:pointer;align-items:center;display:flex}.checkbox-label input[type=checkbox][data-v-56e1ea]{width:auto;margin-right:8px}.checkbox-text[data-v-56e1ea]{color:#555;font-size:.9rem}.field-description[data-v-56e1ea]{color:#777;margin-top:5px;font-size:.8rem;line-height:1.4;display:block}.form-actions[data-v-56e1ea]{justify-content:flex-end;gap:10px;margin-top:10px;display:flex}.btn[data-v-56e1ea]{cursor:pointer;border:none;border-radius:4px;padding:8px 16px;font-size:.9rem;transition:background-color .2s}.btn-primary[data-v-56e1ea]{color:#fff;background-color:#4a90e2}.btn-primary[data-v-56e1ea]:hover:not(:disabled){background-color:#357abd}.btn-secondary[data-v-56e1ea]{color:#fff;background-color:#6c757d}.btn-secondary[data-v-56e1ea]:hover:not(:disabled){background-color:#545b62}.btn[data-v-56e1ea]:disabled{opacity:.6;cursor:not-allowed}.form-toggle-btn[data-v-56e1ea]{cursor:pointer;color:#555;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:5px;display:flex}.form-toggle-btn[data-v-56e1ea]:hover{background-color:#f0f0f0}.form-toggle-btn.active[data-v-56e1ea]{color:#4a90e2;background-color:#4a90e21a}.required[data-v-56e1ea]{color:#e53935;margin-left:2px}.form-readonly[data-v-56e1ea]{padding:10px 0}.form-field-readonly[data-v-56e1ea]{border-bottom:1px solid #eee;margin-bottom:8px;padding-bottom:8px;display:flex}.field-label[data-v-56e1ea]{color:#555;flex:0 0 30%;padding-right:10px;font-weight:500}.field-value[data-v-56e1ea]{word-break:break-word;flex:1}.text-value[data-v-56e1ea]{white-space:pre-wrap}.chat-input-container[data-v-53776c]{background-color:#fff;border-top:1px solid #e0e0e0;width:100%;padding:10px;font-family:Arial,sans-serif;font-size:14px}.chat-input[data-v-53776c]{align-items:flex-end;gap:10px;display:flex}.input-main[data-v-53776c]{flex:1;position:relative}.message-input[data-v-53776c]{resize:none;border:1px solid #ddd;border-radius:20px;outline:none;width:100%;min-height:40px;padding:10px 40px 10px 15px;font-family:Arial,sans-serif;font-size:14px;transition:border-color .2s}.message-input[data-v-53776c]:focus{border-color:#0084ff}.message-input.over-limit[data-v-53776c]{border-color:#ff4d4f}.character-counter[data-v-53776c]{color:#999;font-size:12px;position:absolute;bottom:10px;right:10px}.character-counter.over-limit[data-v-53776c]{color:#ff4d4f}.input-actions[data-v-53776c]{align-items:center;gap:8px;display:flex}.send-btn[data-v-53776c]{color:#fff;cursor:pointer;background-color:#0084ff;border:none;border-radius:50%;justify-content:center;align-items:center;width:40px;height:40px;transition:background-color .2s;display:flex}.send-btn[data-v-53776c]:hover{background-color:#0077e6}.send-btn[data-v-53776c]:disabled{cursor:not-allowed;background-color:#ccc}.send-btn.form-mode[data-v-53776c]{background-color:#4caf50}.send-btn.form-mode[data-v-53776c]:hover{background-color:#43a047}.loading-spinner[data-v-53776c]{animation:1s linear infinite spin-53776c;display:inline-block}@keyframes spin-53776c{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.dynamic-form-container[data-v-53776c]{background-color:#f9f9f9;border:1px solid #ddd;border-radius:8px;margin-bottom:10px;padding:15px 15px 5px;font-family:Arial,sans-serif;font-size:14px;position:relative;box-shadow:0 2px 4px #0000000d}.progress-tracker[data-v-237cbd]{background-color:#f9f9f9;border:1px solid #e0e0e0;border-radius:8px;margin:10px 0;font-family:Arial,sans-serif;font-size:14px;transition:all .3s}.progress-tracker.expanded[data-v-237cbd]{box-shadow:0 2px 8px #0000001a}.progress-tracker.completed[data-v-237cbd]{background-color:#f1f8e9;border-color:#4caf50}.progress-tracker.error[data-v-237cbd]{background-color:#ffebee;border-color:#f44336}.progress-header[data-v-237cbd]{cursor:pointer;background-color:#fff;border-bottom:1px solid #e0e0e0;border-radius:8px 8px 0 0;justify-content:space-between;align-items:center;padding:12px 15px;transition:background-color .2s;display:flex}.progress-header[data-v-237cbd]:hover{background-color:#f5f5f5}.progress-title[data-v-237cbd]{color:#333;align-items:center;font-weight:500;display:flex}.status-icon[data-v-237cbd]{margin-right:8px;font-size:16px;font-weight:700}.status-icon.completed[data-v-237cbd]{color:#4caf50}.status-icon.error[data-v-237cbd]{color:#f44336}.status-icon.in-progress[data-v-237cbd]{color:#2196f3}.spinner[data-v-237cbd]{border:2px solid #f3f3f3;border-top-color:#2196f3;border-radius:50%;width:16px;height:16px;margin-right:8px;animation:1s linear infinite spin-237cbd;display:inline-block}@keyframes spin-237cbd{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.progress-toggle[data-v-237cbd]{color:#666;font-size:12px;transition:transform .2s}.progress-tracker.expanded .progress-toggle[data-v-237cbd]{transform:rotate(180deg)}.progress-error[data-v-237cbd]{color:#c62828;background-color:#ffcdd2;border-top:1px solid #e0e0e0;padding:10px 15px;font-size:13px}.progress-content[data-v-237cbd]{background-color:#fff;border-radius:0 0 8px 8px;max-height:200px;padding:10px 15px;transition:max-height .3s;overflow-y:auto}.progress-content.single-line[data-v-237cbd]{max-height:40px;overflow:hidden}.progress-line[data-v-237cbd]{color:#555;word-break:break-word;padding:2px 0;font-size:13px;line-height:1.4}.progress-line[data-v-237cbd]:last-child{color:#333;font-weight:500}.progress-content[data-v-237cbd]::-webkit-scrollbar{width:4px}.progress-content[data-v-237cbd]::-webkit-scrollbar-track{background:#f1f1f1;border-radius:2px}.progress-content[data-v-237cbd]::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:2px}.progress-content[data-v-237cbd]::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (width<=768px){.progress-header[data-v-237cbd]{padding:10px 12px}.progress-content[data-v-237cbd]{max-height:150px;padding:8px 12px}.progress-content.single-line[data-v-237cbd]{max-height:35px}.progress-line[data-v-237cbd]{font-size:12px}}.progress-line[data-v-237cbd]{animation:.3s ease-in fadeIn-237cbd}@keyframes fadeIn-237cbd{0%{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:translateY(0)}}.message[data-v-375170]{width:auto;max-width:90%;margin-bottom:15px}.message.user[data-v-375170]{margin-left:auto}.message.ai[data-v-375170]{margin-right:auto}.message-content[data-v-375170]{width:100%;font-family:Arial,sans-serif;font-size:14px}.form-display[data-v-375170]{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin:15px 0;padding:15px;font-family:inherit}.form-result-table[data-v-375170]{border-collapse:collapse;width:100%;font-family:inherit}.form-result-table th[data-v-375170]{text-align:left;border-bottom:1px solid #e0e0e0;padding:8px;font-family:Arial,sans-serif;font-size:14px;font-weight:600}.form-result-table td[data-v-375170]{border-bottom:1px solid #f0f0f0;padding:8px;font-family:Arial,sans-serif;font-size:14px}.form-result-table td[data-v-375170]:first-child{width:35%;font-weight:500}.form-result-table input.form-input[data-v-375170],.form-result-table textarea.form-textarea[data-v-375170],.form-result-table select.form-select[data-v-375170]{background-color:#fff;border:1px solid #ddd;border-radius:4px;width:100%;padding:6px;font-family:Arial,sans-serif;font-size:14px}.form-result-table textarea.form-textarea[data-v-375170]{resize:vertical;min-height:60px}.form-result-table .field-label[data-v-375170]{vertical-align:top;border-bottom:1px solid #f0f0f0;width:35%;padding:8px;font-weight:500}.form-result-table .field-value[data-v-375170]{vertical-align:top;border-bottom:1px solid #f0f0f0;padding:8px}.toggle-switch[data-v-375170]{width:50px;height:24px;display:inline-block;position:relative}.toggle-input[data-v-375170]{opacity:0;width:0;height:0}.toggle-slider[data-v-375170]{cursor:pointer;background-color:#ccc;border-radius:24px;transition:all .4s;position:absolute;inset:0}.toggle-knob[data-v-375170]{content:"";background-color:#fff;border-radius:50%;width:18px;height:18px;transition:all .4s;position:absolute;bottom:3px;left:3px}.material-symbols-outlined[data-v-375170]{vertical-align:middle;margin-right:8px;font-size:20px}.form-header[data-v-375170]{border-bottom:1px solid #e0e0e0;align-items:center;padding:8px;display:flex}.message-text[data-v-375170]{white-space:pre-wrap;word-break:break-word;font-family:Arial,sans-serif;font-size:14px}.form-error[data-v-375170]{color:red;padding:10px;font-family:Arial,sans-serif;font-size:14px}.error-content[data-v-375170]{background-color:#ffebee;border:1px solid #f44336;border-radius:4px;padding:10px}.retry-btn[data-v-375170]{color:#fff;cursor:pointer;background-color:#f44336;border:none;border-radius:4px;margin-top:10px;padding:8px 16px;font-size:14px}.retry-btn[data-v-375170]:hover{background-color:#d32f2f}@media (width<=768px){.message[data-v-375170]{max-width:95%}.form-result-table td[data-v-375170]:first-child{width:40%}}.typing-indicator[data-v-a78025]{background-color:#f0f0f0;border-radius:18px;align-items:center;width:fit-content;max-width:80px;margin:10px 0;padding:10px 15px;display:flex}.typing-dot[data-v-a78025]{background-color:#999;border-radius:50%;width:8px;height:8px;margin:0 2px;animation:1.4s ease-in-out infinite typing-bounce-a78025}.typing-dot[data-v-a78025]:first-child{animation-delay:-.32s}.typing-dot[data-v-a78025]:nth-child(2){animation-delay:-.16s}.typing-dot[data-v-a78025]:nth-child(3){animation-delay:0s}.typing-text[data-v-a78025]{color:#666;margin-left:10px;font-size:.9rem;font-style:italic}@keyframes typing-bounce-a78025{0%,80%,to{opacity:.5;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}@keyframes typing-pulse-a78025{0%,60%,to{transform:initial;opacity:.4}30%{opacity:1;transform:scale(1.2)}}@media (width<=768px){.typing-indicator[data-v-a78025]{margin:8px 0;padding:8px 12px}.typing-dot[data-v-a78025]{width:6px;height:6px}.typing-text[data-v-a78025]{margin-left:8px;font-size:.8rem}}@media (prefers-color-scheme:dark){.typing-indicator[data-v-a78025]{background-color:#2a2a2a}.typing-dot[data-v-a78025]{background-color:#ccc}.typing-text[data-v-a78025]{color:#aaa}}@media (prefers-contrast:high){.typing-indicator[data-v-a78025]{border:1px solid #333}.typing-dot[data-v-a78025]{background-color:#000}.typing-text[data-v-a78025]{color:#000}}@media (prefers-reduced-motion:reduce){.typing-dot[data-v-a78025]{opacity:.7;animation:none}.typing-indicator[data-v-a78025]{opacity:.8}}.message-history-container[data-v-05325c]{flex-direction:column;height:100%;display:flex;overflow:hidden}.chat-messages[data-v-05325c]{scroll-behavior:smooth;flex:1;padding:10px;overflow-y:auto}.load-more-indicator[data-v-05325c]{text-align:center;color:#666;padding:10px;font-size:.9rem}.empty-state[data-v-05325c]{text-align:center;color:#666;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:40px 20px;display:flex}.empty-icon[data-v-05325c]{opacity:.5;margin-bottom:16px;font-size:3rem}.empty-text[data-v-05325c]{color:#333;margin-bottom:8px;font-size:1.2rem;font-weight:500}.empty-subtext[data-v-05325c]{color:#666;max-width:300px;font-size:.9rem;line-height:1.4}.chat-messages[data-v-05325c]::-webkit-scrollbar{width:6px}.chat-messages[data-v-05325c]::-webkit-scrollbar-track{background:#f1f1f1;border-radius:3px}.chat-messages[data-v-05325c]::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:3px}.chat-messages[data-v-05325c]::-webkit-scrollbar-thumb:hover{background:#a8a8a8}@media (width<=768px){.chat-messages[data-v-05325c]{padding:8px}.empty-state[data-v-05325c]{padding:20px 16px}.empty-icon[data-v-05325c]{font-size:2.5rem}.empty-text[data-v-05325c]{font-size:1.1rem}}.form-message[data-v-199543]{background-color:#f5f5f5b3;border:1px solid #e0e0e0;border-radius:8px;margin-bottom:12px;padding:12px}.form-message-header[data-v-199543]{border-bottom:1px solid #e0e0e0;align-items:center;margin-bottom:10px;padding-bottom:8px;display:flex}.form-message-icon[data-v-199543]{color:#555;margin-right:8px;font-size:18px}.form-message-title[data-v-199543]{color:#333;font-size:1em;font-weight:600}.form-message-fields[data-v-199543]{flex-direction:column;gap:8px;display:flex}.form-message-field[data-v-199543]{border-bottom:1px solid #0000000d;justify-content:space-between;align-items:flex-start;padding:4px 0;display:flex}.form-message-field[data-v-199543]:last-child{border-bottom:none}.field-message-label[data-v-199543]{color:#555;word-break:break-word;flex:0 0 40%;padding-right:10px;font-weight:500}.field-message-value[data-v-199543]{color:#333;word-break:break-word;text-align:right;flex:1}.field-message-value.text-value[data-v-199543]{white-space:pre-wrap;text-align:left}.message.user .form-message[data-v-199543]{background-color:#ffffff1a}.message.ai .form-message[data-v-199543]{background-color:#f5f5fab3}.form-display[data-v-199543]{background-color:#00000008;border:1px solid #0000001a;border-radius:8px;margin-bottom:10px;padding:12px}.user-form-values[data-v-199543]{background-color:#007bff0d}.user-form .form-field[data-v-199543]{margin-bottom:6px!important}.user-form .field-label[data-v-199543]{color:#555!important;padding:2px 0!important;font-weight:500!important}.user-form .field-value[data-v-199543]{padding:2px 0!important}.read-only .form-field[data-v-199543]:hover{background-color:#0000}.dynamic-form.read-only .form-fields[data-v-199543]{border-top:1px solid #0000000d;margin-top:10px;padding-top:8px}.message-form .form-title[data-v-199543]{font-size:1em!important}.message-form .form-description[data-v-199543]{font-size:.85em!important}.form-readonly[data-v-199543]{width:100%}.form-readonly .field-label[data-v-199543]{color:#555;font-weight:500}.form-readonly .field-value[data-v-199543]{word-break:break-word}.form-readonly .text-value[data-v-199543]{white-space:pre-wrap}.message-text[data-v-199543]{white-space:pre-wrap;word-break:break-word}.message-content[data-v-199543]{max-width:100%}@media (width<=768px){.form-message-field[data-v-199543]{flex-direction:column;align-items:flex-start;gap:4px}.field-message-label[data-v-199543]{flex:none;padding-right:0}.field-message-value[data-v-199543]{text-align:left}.form-message[data-v-199543]{padding:10px}.form-message-title[data-v-199543]{font-size:.9em}}@media (prefers-color-scheme:dark){.form-message[data-v-199543]{background-color:#282828b3;border-color:#555}.form-message-header[data-v-199543]{border-bottom-color:#555}.form-message-title[data-v-199543]{color:#e0e0e0}.field-message-label[data-v-199543]{color:#ccc}.field-message-value[data-v-199543]{color:#e0e0e0}.form-message-field[data-v-199543]{border-bottom-color:#ffffff1a}}@media (prefers-contrast:high){.form-message[data-v-199543]{background-color:#fff;border:2px solid #000}.form-message-title[data-v-199543],.field-message-label[data-v-199543],.field-message-value[data-v-199543]{color:#000}.form-message-header[data-v-199543]{border-bottom:2px solid #000}}.chat-app-container[data-v-bfe173]{flex-direction:column;width:100%;height:100vh;display:flex}.chat-messages-area[data-v-bfe173]{flex:1;overflow:hidden}.chat-input-area[data-v-bfe173]{flex-shrink:0}@media (width<=768px){.chat-app-container[data-v-bfe173]{height:100vh}}.sidebar-logo[data-v-81ad23]{border-bottom:1px solid #ffffff1a;justify-content:center;align-items:center;padding:20px 15px;display:flex}.logo-image[data-v-81ad23]{object-fit:contain;max-width:100%;max-height:60px}.logo-placeholder[data-v-81ad23]{background:var(--primary-color);color:#fff;border-radius:50%;justify-content:center;align-items:center;width:50px;height:50px;font-size:1.2rem;font-weight:700;display:flex}.sidebar-make-name[data-v-17a3aa]{text-align:center;border-bottom:1px solid #ffffff1a;padding:15px}.make-name-text[data-v-17a3aa]{color:var(--sidebar-color);margin:0;font-size:1.1rem;font-weight:600;line-height:1.3}.make-subtitle[data-v-17a3aa]{color:#fffc;margin-top:5px;font-size:.85rem}.sidebar-explanation[data-v-609dcb]{flex:1;padding:15px;overflow-y:auto}.explanation-loading[data-v-609dcb]{color:var(--sidebar-color);text-align:center;padding:20px;font-style:italic}.explanation-content[data-v-609dcb]{color:var(--sidebar-color);font-size:.9rem;line-height:1.5}.explanation-content[data-v-609dcb] h1,.explanation-content[data-v-609dcb] h2,.explanation-content[data-v-609dcb] h3{color:var(--sidebar-color);margin-top:1rem;margin-bottom:.5rem}.explanation-content[data-v-609dcb] p{margin-bottom:1rem}.explanation-content[data-v-609dcb] strong{color:var(--primary-color);font-weight:600}.sidebar[data-v-11fec9]{background:var(--sidebar-background);height:100%;color:var(--sidebar-color);flex-direction:column;width:300px;min-width:250px;display:flex}@media (width<=768px){.sidebar[data-v-11fec9]{width:100%;min-width:unset}} \ No newline at end of file diff --git a/test_sidebar_components.js b/test_sidebar_components.js new file mode 100644 index 0000000..3608023 --- /dev/null +++ b/test_sidebar_components.js @@ -0,0 +1,185 @@ +// Test script for SideBar Vue components +// This can be run in the browser console when the chat client is loaded + +console.log('Testing SideBar Vue components...'); + +// Test if all components are available +function testComponentAvailability() { + console.log('๐Ÿงช Testing component availability...'); + + // Check if Vue is available + if (!window.Vue || !window.Vue.createApp) { + console.error('โŒ Vue is not available'); + return false; + } + console.log('โœ… Vue is available'); + + // Check if chatConfig is available + if (!window.chatConfig) { + console.error('โŒ window.chatConfig is not available'); + return false; + } + console.log('โœ… window.chatConfig is available'); + + // Check if sidebar container exists + const container = document.getElementById('sidebar-container'); + if (!container) { + console.error('โŒ #sidebar-container not found in DOM'); + return false; + } + console.log('โœ… #sidebar-container found in DOM'); + + return true; +} + +// Test sidebar component props +function testSidebarProps() { + console.log('๐Ÿงช Testing sidebar props...'); + + const expectedProps = { + tenantMake: window.chatConfig.tenantMake || {}, + explanationText: window.chatConfig.explanation || '', + initialLanguage: window.chatConfig.language || 'nl', + supportedLanguageDetails: window.chatConfig.supportedLanguageDetails || {}, + allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'], + apiPrefix: window.chatConfig.apiPrefix || '' + }; + + console.log('๐Ÿ“‹ Expected props:', expectedProps); + + // Validate props + if (!expectedProps.tenantMake.name) { + console.warn('โš ๏ธ tenantMake.name is empty'); + } + + if (!expectedProps.explanationText) { + console.warn('โš ๏ธ explanationText is empty'); + } + + if (!expectedProps.supportedLanguageDetails || Object.keys(expectedProps.supportedLanguageDetails).length === 0) { + console.warn('โš ๏ธ supportedLanguageDetails is empty, using defaults'); + } + + console.log('โœ… Props validation completed'); + return expectedProps; +} + +// Test if old sidebar elements are removed +function testOldSidebarCleanup() { + console.log('๐Ÿงช Testing old sidebar cleanup...'); + + const oldElements = [ + 'sidebar-explanation', + 'language-selector-container' + ]; + + let foundOldElements = false; + oldElements.forEach(id => { + const element = document.getElementById(id); + if (element) { + console.warn(`โš ๏ธ Old element #${id} still exists in DOM`); + foundOldElements = true; + } + }); + + if (!foundOldElements) { + console.log('โœ… No old sidebar elements found - cleanup successful'); + } + + return !foundOldElements; +} + +// Test translation functionality +async function testTranslationIntegration() { + console.log('๐Ÿงช Testing translation integration...'); + + try { + // Test if translation API is accessible + const response = await fetch('/api/translate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }, + credentials: 'same-origin', + body: JSON.stringify({ + text: 'Test translation', + target_lang: 'nl', + context: 'sidebar_test' + }) + }); + + if (response.ok) { + const result = await response.json(); + console.log('โœ… Translation API is accessible:', result.success ? 'Success' : 'Failed'); + return result.success; + } else { + console.warn('โš ๏ธ Translation API returned error:', response.status); + return false; + } + } catch (error) { + console.warn('โš ๏ธ Translation API test failed:', error.message); + return false; + } +} + +// Run all tests +async function runAllSidebarTests() { + console.log('๐Ÿš€ Starting SideBar component tests...'); + + console.log('\n1. Testing component availability...'); + const availabilityTest = testComponentAvailability(); + + console.log('\n2. Testing sidebar props...'); + const props = testSidebarProps(); + + console.log('\n3. Testing old sidebar cleanup...'); + const cleanupTest = testOldSidebarCleanup(); + + console.log('\n4. Testing translation integration...'); + const translationTest = await testTranslationIntegration(); + + console.log('\n๐Ÿ“Š Test Results Summary:'); + console.log(`Component Availability: ${availabilityTest ? 'โœ…' : 'โŒ'}`); + console.log(`Props Validation: โœ…`); + console.log(`Old Sidebar Cleanup: ${cleanupTest ? 'โœ…' : 'โš ๏ธ'}`); + console.log(`Translation Integration: ${translationTest ? 'โœ…' : 'โš ๏ธ'}`); + + const allPassed = availabilityTest && cleanupTest && translationTest; + console.log(`\n๐ŸŽฏ Overall Status: ${allPassed ? 'โœ… All tests passed!' : 'โš ๏ธ Some tests need attention'}`); + + return { + availability: availabilityTest, + cleanup: cleanupTest, + translation: translationTest, + props: props, + overall: allPassed + }; +} + +// Export for manual testing +if (typeof window !== 'undefined') { + window.testComponentAvailability = testComponentAvailability; + window.testSidebarProps = testSidebarProps; + window.testOldSidebarCleanup = testOldSidebarCleanup; + window.testTranslationIntegration = testTranslationIntegration; + window.runAllSidebarTests = runAllSidebarTests; + + console.log('SideBar test functions available:'); + console.log('- testComponentAvailability()'); + console.log('- testSidebarProps()'); + console.log('- testOldSidebarCleanup()'); + console.log('- testTranslationIntegration()'); + console.log('- runAllSidebarTests()'); +} + +// Auto-run if in Node.js environment (for CI/CD) +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + testComponentAvailability, + testSidebarProps, + testOldSidebarCleanup, + testTranslationIntegration, + runAllSidebarTests + }; +} \ No newline at end of file diff --git a/test_translation_simple.js b/test_translation_simple.js new file mode 100644 index 0000000..92b7911 --- /dev/null +++ b/test_translation_simple.js @@ -0,0 +1,143 @@ +// Simple test script for the updated useTranslation composable +// This can be run in the browser console when the chat client is loaded + +console.log('Testing updated useTranslation composable...'); + +// Test basic translation functionality +async function testTranslation() { + try { + // Test direct API call to /api/translate + const response = await fetch('/api/translate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }, + credentials: 'same-origin', + body: JSON.stringify({ + text: 'Hello world', + target_lang: 'nl', + source_lang: 'en', + context: 'test' + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + console.log('โœ… Direct API test successful:', result); + + if (result.success) { + console.log('โœ… Translation successful:', result.translated_text); + } else { + console.log('โŒ Translation failed:', result.error); + } + + return result; + } catch (error) { + console.error('โŒ Direct API test failed:', error); + return null; + } +} + +// Test empty text handling +async function testEmptyText() { + try { + const response = await fetch('/api/translate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }, + credentials: 'same-origin', + body: JSON.stringify({ + text: '', + target_lang: 'nl' + }) + }); + + const result = await response.json(); + console.log('โœ… Empty text test result:', result); + + if (!result.success && result.error.includes('empty')) { + console.log('โœ… Empty text validation working correctly'); + } + + return result; + } catch (error) { + console.error('โŒ Empty text test failed:', error); + return null; + } +} + +// Test missing parameters +async function testMissingParams() { + try { + const response = await fetch('/api/translate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest' + }, + credentials: 'same-origin', + body: JSON.stringify({ + text: 'Hello world' + // Missing target_lang + }) + }); + + const result = await response.json(); + console.log('โœ… Missing params test result:', result); + + if (!result.success && result.error.includes('missing')) { + console.log('โœ… Parameter validation working correctly'); + } + + return result; + } catch (error) { + console.error('โŒ Missing params test failed:', error); + return null; + } +} + +// Run all tests +async function runAllTests() { + console.log('๐Ÿš€ Starting translation API tests...'); + + console.log('\n1. Testing basic translation...'); + await testTranslation(); + + console.log('\n2. Testing empty text validation...'); + await testEmptyText(); + + console.log('\n3. Testing missing parameters validation...'); + await testMissingParams(); + + console.log('\nโœ… All tests completed!'); +} + +// Export for manual testing +if (typeof window !== 'undefined') { + window.testTranslation = testTranslation; + window.testEmptyText = testEmptyText; + window.testMissingParams = testMissingParams; + window.runAllTests = runAllTests; + + console.log('Test functions available:'); + console.log('- testTranslation()'); + console.log('- testEmptyText()'); + console.log('- testMissingParams()'); + console.log('- runAllTests()'); +} + +// Auto-run if in Node.js environment (for CI/CD) +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + testTranslation, + testEmptyText, + testMissingParams, + runAllTests + }; +} \ No newline at end of file