PageSourceSearch

https://static.airaintelligence.ai/apps/airaintelligence/js/modal-assistant.js

js airaintelligence.ai collected 2026-10-05 15:27:20 UTC 69,738 bytes, 1,622 lines download raw bytes

1// Modal Assistant Chat System
2(function() {
3    // Configuration
4    const APP_ID = 'airaintelligence';
5    
6    // Current conversation ID (session state only, not persisted locally)
7    let currentConversationId = null;
8    
9    // Modal Assistant Variables
10    let modalCurrentMode = 'platform';
11    const modalOverlay = document.getElementById('modalAssistantOverlay');
12    const modalTitle = document.getElementById('modalAssistantTitle');
13    const modalMessages = document.querySelector('.modal-chat-messages');
14    const modalEmptyState = document.querySelector('.modal-chat-empty-state');
15    const modalMessageInput = document.getElementById('modalMessageInput');
16    const modalChatForm = document.getElementById('modalChatForm');
17    const modalSendButton = document.getElementById('modalSendButton');
18    const modalSuggestions = document.getElementById('modalSuggestions');
19    const modalVoiceCircle = document.getElementById('voiceCircle');
20    const modalVoiceButton = document.getElementById('modalVoiceButton');
21    const modalEmptyLogo = document.getElementById('modalEmptyLogo');
22    
23    // Modal Sidebar Variables
24    const modalSidebar = document.getElementById('modalAssistantSidebar');
25    const modalSidebarToggle = document.getElementById('modalSidebarToggle');
26    const modalSidebarCloseBtn = document.getElementById('modalSidebarCloseBtn');
27    
28    // Document suggestion variables
29    let modalSearchTimer;
30    
31    // Voice Canvas Animator
32    let voiceAnimator = null;
33    const voiceCanvas = document.getElementById('voiceCanvas');
34    if (voiceCanvas) {
35        // Load voice-canvas.js dynamically
36        const script = document.createElement('script');
37        script.src = "https://static.airaintelligence.ai/apps/airaintelligence/js/voice-canvas.js";
38        script.onload = function() {
39            voiceAnimator = new VoiceCanvasAnimator(voiceCanvas);
40        };
41        document.head.appendChild(script);
42    }
43    
44    // Update modal logo based on theme
45    function updateModalLogo() {
46        if (!modalEmptyLogo) return;
47        
48        const isDark = document.body.classList.contains('dark-mode');
49        const logoSrc = isDark
50            ? 'https://static.airaintelligence.ai/shared/assets/images/logos/logobig_dark.png'
51            : 'https://static.airaintelligence.ai/shared/assets/images/logos/logobig.png';
52        modalEmptyLogo.src = logoSrc;
53    }
54    
55    // Modal Sidebar Toggle Functions - Base.html style
56    function toggleModalSidebar() {
57        if (!modalSidebar) return;
58        
59        if (modalSidebar.classList.contains('show')) {
60            hideModalSidebar();
61        } else {
62            showModalSidebar();
63        }
64    }
65    
66    function showModalSidebar() {
67        if (!modalSidebar) return;
68        modalSidebar.classList.add('show');
69
70        // Mark overlay state (CSS can also hide the toggle)
71        if (modalOverlay) {
72            modalOverlay.classList.add('sidebar-open');
73        }
74        
75        // Hide toggle button when sidebar is open
76        if (modalSidebarToggle) {
77            modalSidebarToggle.style.setProperty('display', 'none', 'important');
78        }
79    }
80    
81    function hideModalSidebar() {
82        if (!modalSidebar) return;
83        modalSidebar.classList.remove('show');
84
85        if (modalOverlay) {
86            modalOverlay.classList.remove('sidebar-open');
87        }
88        
89        // Show toggle button when sidebar is closed
90        if (modalSidebarToggle) {
91            modalSidebarToggle.style.display = 'flex';
92            const icon = modalSidebarToggle.querySelector('i');
93            if (icon) icon.className = 'bi bi-chevron-bar-right';
94        }
95    }
96    
97    // Modal Sidebar Navigation
98    function handleModalNavigation(tab) {
99        // Remove active class from all nav items
100        const navItems = document.querySelectorAll('.modal-nav-item');
101        navItems.forEach(item => item.classList.remove('active'));
102        
103        // Add active class to clicked item
104        const activeItem = document.querySelector(`[data-tab="${tab}"]`);
105        if (activeItem) {
106            activeItem.classList.add('active');
107        }
108        
109        // Handle tab content switching here
110        console.log('Switched to tab:', tab);
111        // TODO: Implement tab content switching
112    }
113    
114    // Voice Circle State Manager
115    function setVoiceCircleState(state) {
116        if (!modalVoiceCircle) return;
117        
118        modalVoiceCircle.className = 'voice-circle ' + state;
119        
120        // Update canvas animation
121        if (voiceAnimator) {
122            voiceAnimator.setState(state);
123        }
124    }
125
126    // Modal Open/Close Functions
127    function openModalAssistant(mode = 'platform') {
128        modalCurrentMode = mode;
129        
130        // Update logo based on current theme
131        updateModalLogo();
132        
133        // Initialize sidebar as hidden
134        if (modalSidebar) {
135            modalSidebar.classList.remove('show'); // Ensure it's hidden
136            if (modalOverlay) {
137                modalOverlay.classList.remove('sidebar-open');
138            }
139            // Set toggle button icon to chevron-bar-right (sidebar hidden) and ensure it's visible
140            if (modalSidebarToggle) {
141                modalSidebarToggle.style.display = 'flex'; // CRITICAL: Ensure button is visible
142                const icon = modalSidebarToggle.querySelector('i');
143                if (icon) icon.className = 'bi bi-chevron-bar-right';
144            }
145        }
146        
147        // Show modal with animation
148        modalOverlay.style.display = 'flex';
149        setTimeout(() => {
150            modalOverlay.classList.add('show');
151            setVoiceCircleState('idle');
151 // Start with idle animation
152            
153            // Start canvas animation
154            if (voiceAnimator) {
155                voiceAnimator.start();
156            }
157        }, 10);
158        
159        // Load conversations when modal opens
160        loadChats();
161        
162        // Focus input
163        setTimeout(() => {
164            modalMessageInput.focus();
165        }, 300);
166    }
167
168    function closeModalAssistant() {
169        // Zoom + Blur closing animation
170        modalOverlay.classList.add('closing');
171        modalOverlay.classList.remove('show');
172        
173        // Stop canvas animation
174        if (voiceAnimator) {
175            voiceAnimator.stop();
176        }
177        
178        // Keep flex layout
179        modalOverlay.style.display = 'flex';
180        modalOverlay.style.alignItems = 'center';
181        modalOverlay.style.justifyContent = 'center';
182        
183        setTimeout(() => {
184            modalOverlay.style.display = 'none';
185            modalOverlay.classList.remove('closing');
186            // Clean style
187            modalOverlay.style.alignItems = '';
188            modalOverlay.style.justifyContent = '';
189        }, 400);
190    }
191
192    // Document suggestion functions
193    function modalGetFileIcon(fileType) {
194        if (modalCurrentMode === 'platform') {
195            return '<i class="bi bi-question-circle"></i>';
196        } else {
197            const icons = {
198                'pdf': '<i class="bi bi-file-earmark-pdf"></i>',
199                'docx': '<i class="bi bi-file-earmark-word"></i>', 
200                'markdown': '<i class="bi bi-file-earmark-text"></i>',
201                'xlsx': '<i class="bi bi-file-earmark-excel"></i>',
202                'pptx': '<i class="bi bi-file-earmark-ppt"></i>',
203                'default': '<i class="bi bi-file-earmark"></i>'
204            };
205            return icons[fileType] || icons.default;
206        }
207    }
208    
209    function modalHideDocumentSuggestions() {
210        if (modalSuggestions) {
211            modalSuggestions.style.display = 'none';
212        }
213        // Show empty state if no messages and input is empty
214        if (modalMessageInput.value.trim() === '' && modalMessages.querySelectorAll('.chat-message').length === 0) {
215            if (modalEmptyState) {
216                modalEmptyState.classList.remove('hidden');
217            }
218        }
219    }
220    
221    function modalSelectDocument(source, title, preview) {
222        let question;
223        
224        if (modalCurrentMode === 'platform') {
225            question = title;
226        } else {
227            if (preview && preview.length > 10) {
228                const content = preview.toLowerCase();
229                
230                if (content.includes('nasıl') || content.includes('işlem')) {
231                    question = `${title} - bu işlem nasıl yapılır?`;
232                } else if (content.includes('rapor') || content.includes('basım')) {
233                    question = `${title} raporu nasıl alınır?`;
234                } else if (content.includes('kurulum') || content.includes('ayar')) {
235                    question = `${title} kurulumu nasıl yapılır?`;
236                } else if (content.includes('entegrasyon') || content.includes('bağlantı')) {
237                    question = `${title} entegrasyonu hakkında bilgi ver`;
238                } else {
239                    const cleanTitle = title.replace(/\.(pdf|docx|md|txt|xlsx|pptx)$/i, '');
240                    question = `${cleanTitle} nedir`;
241                }
242            } else {
243                const cleanName = source.replace(/\.(pdf|docx|md|txt|xlsx|pptx)$/i, '');
244                question = `${cleanName} nedir`;
245            }
246        }
247        
248        modalMessageInput.value = question;
249        modalHideDocumentSuggestions();
250        modalChatForm.dispatchEvent(new Event('submit'));
251    }
252    
253    function modalDisplayDocumentSuggestions(results) {
254        if (!results.suggestions || results.suggestions.length === 0) {
255            modalHideDocumentSuggestions();
256            return;
257        }
258        
259        // Hide empty state when showing suggestions
260        if (modalEmptyState) {
261            modalEmptyState.classList.add('hidden');
262        }
263        
264        const headerText = modalCurrentMode === 'platform' ? 'Platform Help' : 'Suggested Documents';
265        const headerIcon = modalCurrentMode === 'platform' ? '<i class="bi bi-question-circle me-1"></i>' : '<i class="bi bi-lightbulb me-1"></i>';
266        let html = `<div class="modal-suggestions-header">${headerIcon}${headerText}</div>`;
267        
268        results.suggestions.forEach((suggestion, index) => {
269            const icon = modalGetFileIcon(suggestion.file_type);
270            html += `
271                <div class="modal-document-suggestion" data-source="${suggestion.source}" data-title="${suggestion.title}" data-content="${suggestion.content.replace(/"/g, '&quot;').replace(/'/g, '&#39;')}" onclick="modalSelectDocumentFromData(this)">
272                    <div class="modal-suggestion-main">
273                        <span class="modal-suggestion-icon">${icon}</span>
274                        <span class="modal-suggestion-title">${suggestion.title}</span>
275                        <span class="modal-match-percentage">${suggestion.match_percentage}% Match</span>
276                    </div>
277                    <div class="modal-suggestion-preview">${suggestion.content}</div>
278                </div>
279            `;
280        });
281        
282        modalSuggestions.innerHTML = html;
283        modalSuggestions.style.display = 'block';
284    }
285    
286    async function modalSearchDocuments(query) {
287        try {
288            // Note: Update these endpoints when integrating
289            const endpoint = modalCurrentMode === 'platform' ? '/search-platform-help' : '/search-documents';
290            
291            const response = await fetch(endpoint, {
292                method: 'POST',
293                headers: { 'Content-Type': 'application/json' },
294                body: JSON.stringify({ query: query })
295            });
296            
297            const results = await response.json();
298            modalDisplayDocumentSuggestions(results);
299        } catch (error) {
300            console.error('Modal document search error:', error);
301            modalHideDocumentSuggestions();
302        }
303    }
304    
305    function modalSelectDocumentFromData(element) {
306        const source = element.getAttribute('data-source');
307        const title = element.getAttribute('data-title');
308        const content = element.getAttribute('data-content')
309            .replace(/&quot;/g, '"')
310            .replace(/&#39;/g, "'");
311        
312        modalSelectDocument(source, title, content);
313    }
314
315    // Modal Event Listeners
316    document.addEventListener('DOMContentLoaded', function() {
317        // Assistant toggle button (header icon)
318        const assistantToggle = document.getElementById('assistantToggle');
319        if (assistantToggle) {
320            assistantToggle.addEventListener('click', function(e) {
321                e.preventDefault();
322                openModalAssistant('course');
323            });
324        }
325        
326        // Sidebar assistant button
327        const sidebarAssistantToggle = document.getElementById('sidebarAssistantToggle');
328        if (sidebarAssistantToggle) {
329            sidebarAssistantToggle.addEventListener('click', function(e) {
330                e.preventDefault();
331                openModalAssistant('course');
332            });
333        }
334        
335        // Assistant toggle button (login page header icon)
336        const loginAssistantToggle = document.getElementById('loginAssistantToggle');
337        if (loginAssistantToggle) {
338            loginAssistantToggle.addEventListener('click', function(e) {
339                e.preventDefault();
340                openModalAssistant('platform');
341            });
342        }
343        
344        // Close button
345        const closeButton = document.getElementById('modalAssistantClose');
346        if (closeButton) {
347            closeButton.addEventListener('click', closeModalAssistant);
348        }
349
350        // Click outside to close
351        if (modalOverlay) {
352            modalOverlay.addEventListener('click', function(e) {
353                if (e.target === modalOverlay) {
354                    closeModalAssistant();
355                }
356            });
357        }
358
359        // ESC key to close
360        document.addEventListener('keydown', function(e) {
361            if (e.key === 'Escape' && modalOverlay && modalOverlay.classList.contains('show')) {
362                closeModalAssistant();
363            }
364        });
365
366
367        // Real-time document search
368        if (modalMessageInput) {
369            modalMessageInput.addEventListener('input', function() {
370                // Auto-resize
371                this.style.height = 'auto';
372                this.style.height = (this.scrollHeight) + 'px';
373                
374                // Update voice circle state
375                const query = this.value.trim();
376                if (query.length > 0) {
377                    setVoiceCircleState('listening');
378                } else {
379                    setVoiceCircleState('idle');
380                }
381                
382                // Document search
383                if (query.length > 2) {
384                    clearTimeout(modalSearchTimer);
385                    modalSearchTimer = setTimeout(() => {
386                        modalSearchDocuments(query);
387                    }, 300);
388                } else {
389                    modalHideDocumentSuggestions();
390                    if (query.length === 0 && modalEmptyState && !modalEmptyState.classList.contains('hidden')) {
391                        modalEmptyState.style.display = 'block';
392                    }
393                }
394            });
395            
396            // Enter key handler
397            modalMessageInput.addEventListener('keydown', function(event) {
398                if (event.key === 'Enter' && !event.shiftKey) {
399                    event.preventDefault();
400                    modalSendButton.click();
401                }
402            });
403        }
404
405        // Hide suggestions when clicking outside
406        document.addEventListener('click', function(event) {
407            if (modalSuggestions && !modalSuggestions.contains(event.target) && event.target !== modalMessageInput) {
408                modalHideDocumentSuggestions();
409            }
410        });
411        
412        // Voice button (placeholder for future voice feature)
413        if (modalVoiceButton) {
414            modalVoiceButton.addEventListener('click', function() {
415                // TODO: Implement voice recording
416                alert('Voice input feature coming soon!');
417            });
418        }
419
420        // Form submit
421        if (modalChatForm) {
422            modalChatForm.addEventListener('submit', function(e) {
423                e.preventDefault();
424                const messageText = modalMessageInput.value.trim();
425                if (messageText === '') return;
426
427                if (modalEmptyState) {
428                    modalEmptyState.classList.add('hidden');
429                }
430
431                const userMessage = document.createElement('div');
432                userMessage.classList.add('chat-message', 'user-message');
433                userMessage.innerHTML = `<div class="message-bubble"><div class="message-content"></div></div>`;
434                userMessage.querySelector('.message-content').textContent = messageText;
435                modalMessages.appendChild(userMessage);
436
437                const typingIndicator = document.createElement('div');
438                typingIndicator.classList.add('chat-message', 'assistant-message', 'typing-indicator');
439                typingIndicator.innerHTML = `<div class="message-bubble"><div class="dot-flashing"></div></div>`;
440                modalMessages.appendChild(typingIndicator);
441
442                modalMessages.scrollTop = modalMessages.scrollHeight;
443
444                modalMessageInput.value = '';
445                modalMessageInput.style.height = 'auto';
446                modalHideDocumentSuggestions();
447                
448                // Set voice circle to thinking state
449                setVoiceCircleState('thinking');
450
451                // Disable send button during request
452                if (modalSendButton) {
453                    modalSendButton.disabled = true;
454                }
455
456                // Call DAG chat API with conversation_id
457                fetch('/api/ai/chat', {
458                    method: 'POST',
459                    headers: { 
460                        'Content-Type': 'application/json',
461                        'X-Requested-With': 'XMLHttpRequest'
462                    },
463                    body: JSON.stringify({ 
464                        message: messageText, 
465                        mode: modalCurrentMode,
466                        conversation_id: currentConversationId  // NEW: conversation_id support
467                    })
468                })
469                .then(async response => {
470                    console.log('Modal response status:', response.status);
471                    const contentType = response.headers.get('content-type');
472                    
473                    // Check if response is JSON
474                    if (!contentType || !contentType.includes('application/json')) {
475                        const text = await response.text();
476                        console.error('Non-JSON response:', text.substring(0, 200));
477                        throw new Error(`Server returned non-JSON response: ${contentType}`);
478                    }
479                    
480                    if (!response.ok) {
481                        return response.json().then(err => {
482                            throw new Error(err.error || `HTTP error! status: ${response.status}`);
483                        });
484                    }
485                    return response.json();
486                })
487                .then(data => {
488                    console.log('Modal response data:', data);
489                    typingIndicator.remove();
490                    
491                    // NEW: Update currentConversationId from response
492                    // Check both meta.conversation_id and root level conversation_id
493                    if (data.meta && data.meta.conversation_id) {
494                        currentConversationId = data.meta.conversation_id;
495                    } else if (data.conversation_id) {
496                        currentConversationId = data.conversation_id;
497                    }
498                    
499                    // Reload chat list if new conversation was created
500                    if (currentConversationId) {
501                        loadChats();
502                    }
503                    
504                    // Set voice circle to speaking state
505                    setVoiceCircleState('speaking');
506                    
507                    const assistantMessage = document.createElement('div');
508                    assistantMessage.classList.add('chat-message', 'assistant-message');
509                    
510                    // Extract result from DAG response
511                    const result = data.result || {};
512                    const reply = result.reply || result.answer || 'No response generated';
513                    const confidence = result.confidence || 'medium';
514                    
515                    // Markdown parsing (if marked.js is available)
516                    let parsedContent = reply;
517                    if (typeof marked !== 'undefined') {
518                        if (typeof marked.parse === 'function') {
519                            parsedContent = marked.parse(parsedContent);
520                        } else if (typeof marked === 'function') {
521                            parsedContent = marked(parsedContent);
522                        }
523                    }
524                    
525                    let messageContent = `<div class="message-content">${parsedContent}</div>`;
526                    
527                    // Add structured data (tables, charts) if available
528                    if (result.structured_data) {
529                        const structured = result.structured_data;
530                        
531                        // Render tables
532                        if (structured.tables && structured.tables.length > 0) {
533                            structured.tables.forEach(table => {
534                                let tableHtml = '<div class="message-table mt-3"><h6 class="mb-2">' + (table.title || 'Tablo') + '</h6>';
535                                tableHtml += '<div class="table-responsive"><table class="table table-sm table-bordered">';
536                                
537                                if (table.headers && table.headers.length > 0) {
538                                    tableHtml += '<thead><tr>';
539                                    table.headers.forEach(header => {
540                                        tableHtml += `<th>${header}</th>`;
541                                    });
542                                    tableHtml += '</tr></thead>';
543                                }
544                                
545                                if (table.rows && table.rows.length > 0) {
546                                    tableHtml += '<tbody>';
547                                    table.rows.forEach(row => {
548                                        tableHtml += '<tr>';
549                                        row.forEach(cell => {
550                                            tableHtml += `<td>${cell}</td>`;
551                                        });
552                                        tableHtml += '</tr>';
553                                    });
554                                    tableHtml += '</tbody>';
555                                }
556                                
557                                tableHtml += '</table></div></div>';
558                                messageContent += tableHtml;
559                            });
560                        }
561                        
562                        // Render metrics cards
563                        if (structured.metrics && Object.keys(structured.metrics).length > 0) {
564                            let metricsHtml = '<div class="message-metrics mt-3 row g-2">';
565                            Object.entries(structured.metrics).forEach(([key, value]) => {
566                                metricsHtml += `<div class="col-md-3"><div class="card bg-light"><div class="card-body p-2"><small class="text-secondary d-block">${key}</small><strong>${value}</strong></div></div></div>`;
567                            });
568                            metricsHtml += '</div>';
569                            messageContent += metricsHtml;
570                        }
571                        
572                        // Charts with Chart.js
573                        if (structured.charts && structured.charts.length > 0) {
574                            structured.charts.forEach((chart, chartIndex) => {
575                                const chartId = `chart-${Date.now()}-${chartIndex}`;
576                                const chartContainerId = `chart-container-${chartId}`;
577                                
578                                messageContent += `<div class="message-chart mt-3"><h6 class="mb-2"><i class="bi bi-bar-chart me-1"></i>${chart.title || 'Chart'}</h6><div id="${chartContainerId}" class="chart-container" style="position: relative; height: 300px;"><canvas id="${chartId}"></canvas></div></div>`;
579                                
580                                // Render chart after message is added to DOM
581                                setTimeout(() => {
582                                    const canvas = document.getElementById(chartId);
583                                    if (!canvas) return;
584                                    
585                                    const ctx = canvas.getContext('2d');
586                                    const chartData = chart.data || {};
587                                    
588                                    let chartConfig = {};
589                                    
590                                    if (chart.type === 'bar') {
591                                        chartConfig = {
592                                            type: 'bar',
593                                            data: {
594                                                labels: chartData.labels || [],
595                                                datasets: chartData.datasets || [{
596                                                    label: chartData.label || 'Data',
597                                                    data: chartData.values || [],
598                                                    backgroundColor: 'rgba(54, 162, 235, 0.5)',
599                                                    borderColor: 'rgba(54, 162, 235, 1)',
600                                                    borderWidth: 1
601                                                }]
602                                            },
603                                            options: {
604                                                responsive: true,
605                                                maintainAspectRatio: false,
606                                                plugins: {
607                                                    legend: {
608                                                        display: chartData.datasets && chartData.datasets.length > 1
609                                                    }
610                                                },
611                                                scales: {
612                                                    y: {
613                                                        beginAtZero: true
614                                                    }
615                                                }
616                                            }
617                                        };
618                                    } else if (chart.type === 'line') {
619                                        chartConfig = {
620                                            type: 'line',
621                                            data: {
622                                                labels: chartData.labels || [],
623                                                datasets: chartData.datasets || [{
624                                                    label: chartData.label || 'Data',
625                                                    data: chartData.values || [],
626                                                    borderColor: 'rgba(75, 192, 192, 1)',
627                                                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
628                                                    tension: 0.1
629                                                }]
630                                            },
631                                            options: {
632                                                responsive: true,
633                                                maintainAspectRatio: false,
634                                                plugins: {
635                                                    legend: {
636                                                        display: chartData.datasets && chartData.datasets.length > 1
637                                                    }
638                                                },
639                                                scales: {
640                                                    y: {
641                                                        beginAtZero: true
642                                                    }
643                                                }
644                                            }
645                                        };
646                                    } else if (chart.type === 'pie') {
647                                        chartConfig = {
648                                            type: 'pie',
649                                            data: {
650                                                labels: chartData.labels || [],
651                                                datasets: [{
652                                                    data: chartData.values || [],
653                                                    backgroundColor: [
654                                                        'rgba(255, 99, 132, 0.5)',
655                                                        'rgba(54, 162, 235, 0.5)',
656                                                        'rgba(255, 206, 86, 0.5)',
657                                                        'rgba(75, 192, 192, 0.5)',
658                                                        'rgba(153, 102, 255, 0.5)',
659                                                        'rgba(255, 159, 64, 0.5)'
660                                                    ],
661                                                    borderColor: [
662                                                        'rgba(255, 99, 132, 1)',
663                                                        'rgba(54, 162, 235, 1)',
664                                                        'rgba(255, 206, 86, 1)',
665                                                        'rgba(75, 192, 192, 1)',
666                                                        'rgba(153, 102, 255, 1)',
667                                                        'rgba(255, 159, 64, 1)'
668                                                    ],
669                                                    borderWidth: 1
670                                                }]
671                                            },
672                                            options: {
673                                                responsive: true,
674                                                maintainAspectRatio: false,
675                                                plugins: {
676                                                    legend: {
677                                                        position: 'right'
678                                                    }
679                                                }
680                                            }
681                                        };
682                                    }
683                                    
684                                    if (typeof Chart !== 'undefined' && chartConfig.type) {
685                                        new Chart(ctx, chartConfig);
686                                    }
687                                }, 100);
688                            });
689                        }
690                    }
691                    
692                    // Add summary if available
693                    if (result.summary) {
694                        messageContent += `<div class="message-summary mt-2 pt-2 border-top"><small class="text-secondary"><strong>Özet:</strong> ${result.summary}</small></div>`;
695                    }
696                    
697                    // Add suggested actions if available
698                    if (result.suggested_actions && result.suggested_actions.length > 0) {
699                        let actionsHtml = '<div class="message-actions mt-2 pt-2 border-top"><small class="text-secondary fw-bold"><i class="bi bi-lightbulb me-1"></i>Önerilen İşlemler</small><ul class="mb-0 mt-1">';
700                        result.suggested_actions.forEach(action => {
701                            actionsHtml += `<li class="text-secondary"><small>${action}</small></li>`;
702                        });
703                        actionsHtml += '</ul></div>';
704                        messageContent += actionsHtml;
705                    }
706                    
707                    // Add insights if available
708                    if (result.insights && result.insights.used_tools && result.insights.used_tools.length > 0) {
709                        let toolsHtml = '<div class="message-tools mt-2 pt-2 border-top"><small class="text-secondary"><i class="bi bi-tools me-1"></i>Kullanılan Araçlar: ';
710                        toolsHtml += result.insights.used_tools.join(', ');
711                        toolsHtml += '</small></div>';
712                        messageContent += toolsHtml;
713                    }
714                    
715                    // Add confidence badge
716                    const confidenceColors = {
717                        'high': 'success',
718                        'medium': 'warning',
719                        'low': 'danger'
720                    };
721                    const confidenceColor = confidenceColors[confidence] || 'secondary';
722                    messageContent += `<div class="message-confidence mt-2"><span class="badge bg-${confidenceColor}">Güven: ${confidence}</span></div>`;
723                    
724                    assistantMessage.innerHTML = `<div class="message-bubble">${messageContent}</div>`;
725                    modalMessages.appendChild(assistantMessage);
726                    
727                    // Code highlighting (if hljs is available)
728                    if (typeof hljs !== 'undefined') {
729                        assistantMessage.querySelectorAll('pre code').forEach((block) => {
730                            hljs.highlightElement(block);
731                        });
732                    }
733
734                    modalMessages.scrollTop = modalMessages.scrollHeight;
735                    
736                    // Return to idle state after response
737                    setTimeout(() => {
738                        setVoiceCircleState('idle');
739                    }, 2000);
740                })
741                .catch(error => {
742                    console.error('Modal fetch error:', error);
743                    typingIndicator.remove();
744                    
745                    // Error message
746                    const errorMessage = document.createElement('div');
747                    errorMessage.classList.add('chat-message', 'assistant-message');
748                    const errorText = error.message || 'Bir hata oluştu. Lütfen tekrar deneyin.';
749                    errorMessage.innerHTML = `<div class="message-bubble"><div class="message-content text-danger"><i class="bi bi-exclamation-triangle me-1"></i>${errorText}</div></div>`;
750                    modalMessages.appendChild(errorMessage);
751                    modalMessages.scrollTop = modalMessages.scrollHeight;
752                    
753                    // Return to idle state on error
754                    setVoiceCircleState('idle');
755                })
756                .finally(() => {
757                    // Re-enable send button
758                    if (modalSendButton) {
759                        modalSendButton.disabled = false;
760                    }
761                });
762            });
763        }
764    });
765
766    // Modal Sidebar Event Listeners
767    function initModalSidebarEvents() {
768        // Sidebar toggle button
769        if (modalSidebarToggle) {
770            modalSidebarToggle.addEventListener('click', function(e) {
771                e.preventDefault();
772                toggleModalSidebar();
773            });
774        }
775        
776        // Sidebar close button
777        if (modalSidebarCloseBtn) {
778            modalSidebarCloseBtn.addEventListener('click', function(e) {
779                e.preventDefault();
780                e.stopPropagation(); // Prevent nav item click
781                hideModalSidebar();
782            });
783        }
784        
785        // Navigation items
786        const navItems = document.querySelectorAll('.modal-nav-item');
787        navItems.forEach(item => {
788            item.addEventListener('click', function(e) {
789                e.preventDefault();
790                const tab = this.getAttribute('data-tab');
791                if (tab) {
792                    handleModalNavigation(tab);
793                }
794            });
795        });
796    }
797    
798    // Global functions for onclick events
799    window.modalSelectDocument = modalSelectDocument;
800    window.modalSelectDocumentFromData = modalSelectDocumentFromData;
801    window.openModalAssistant = openModalAssistant;
802    window.closeModalAssistant = closeModalAssistant;
803    
804    // Initialize modal sidebar
805    initModalSidebarEvents();
806    
807    // Initialize logo on page load
808    updateModalLogo();
809    
810    // Load conversations from API
811    async function loadChats() {
812        console.log(`[DEBUG] Loading chats with APP_ID: ${APP_ID}`);
813        console.log(`[DEBUG] Full URL: /api/chat/conversations?app_id=${APP_ID}`);
814        try {
815            const response = await fetch(
816                `/api/chat/conversations?app_id=${APP_ID}`,
817                {
818                    credentials: 'include',
819                    headers: {
820                        'Content-Type': 'application/json'
821                    }
822                }
823            );
824            
825            if (!response.ok) {
826                const errorText = await response.text();
827                console.error(`[DEBUG] Chat API HTTP ${response.status} error:`, errorText);
828                throw new Error(`HTTP ${response.status}`);
829            }
830            
831            const data = await response.json();
832            const conversations = data.conversations || [];
833            
834            // Convert API format to UI format
835            allChats = conversations.map(conv => ({
836                id: conv.id,
837                title: conv.title || 'Yeni Sohbet',
838                preview: '', // Will be populated from first message if needed
839                date: conv.updated_at ? conv.updated_at.split('T')[0] : conv.created_at.split('T')[0],
840                timestamp: conv.updated_at ? new Date(conv.updated_at).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' }) : '',
841                content: '' // Not needed for list view
842            }));
843            
844            filteredChats = [...allChats];
845            renderChatList(filteredChats);
846        } catch (error) {
847            console.error('Error loading chats:', error);
848            // Fallback to empty state
849            allChats = [];
850            filteredChats = [];
851            renderChatList([]);
852        }
853    }
854    
855    // Load messages for a conversation
856    async function loadMessages(conversationId) {
857        try {
858            const response = await fetch(
859                `/api/chat/conversation/${conversationId}/messages`,
860                {
861                    credentials: 'include',
862                    headers: {
863                        'Content-Type': 'application/json'
864                    }
865                }
866            );
867            
868            if (!response.ok) {
869                throw new Error(`HTTP ${response.status}`);
870            }
871            
872            const data = await response.json();
873            const messages = data.messages || [];
874            
875            // Clear current messages
876            if (modalMessages) {
877                modalMessages.innerHTML = '';
878            }
879            
880            // Hide empty state
881            if (modalEmptyState) {
882                modalEmptyState.classList.add('hidden');
883            }
884            
885            // Render messages
886            messages.forEach(msg => {
887                addMessageToChat(msg.message, msg.role);
888            });
889            
890            currentConversationId = conversationId;
891        } catch (error) {
892            console.error('Error loading messages:', error);
893        }
894    }
895    
896    // Search conversations
897    async function searchChats(query) {
898        if (!query || query.trim().length === 0) {
899            loadChats(); // Load all chats if query is empty
900            return;
901        }
902        
903        try {
904            const response = await fetch(
905                `/api/chat/conversations/search?q=${encodeURIComponent(query)}&app_id=${APP_ID}`,
906                {
907                    credentials: 'include',
908                    headers: {
909                        'Content-Type': 'application/json'
910                    }
911                }
912            );
913            
914            if (!response.ok) {
915                throw new Error(`HTTP ${response.status}`);
916            }
917            
918            const data = await response.json();
919            const conversations = data.conversations || [];
920            
921            // Convert API format to UI format
922            filteredChats = conversations.map(conv => ({
923                id: conv.id,
924                title: conv.title || 'Yeni Sohbet',
925                preview: '',
926                date: conv.updated_at ? conv.updated_at.split('T')[0] : conv.created_at.split('T')[0],
927                timestamp: conv.updated_at ? new Date(conv.updated_at).toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' }) : '',
928                content: ''
929            }));
930            
931        renderChatList(filteredChats);
932        } catch (error) {
933            console.error('Error searching chats:', error);
934            renderChatList([]);
935        }
936    }
937    
938    // Render chat list
939    function renderChatList(chats) {
940        const chatList = document.getElementById('modalChatList');
941        if (!chatList) return;
942        
943        if (!chats.length) {
944            chatList.innerHTML = '<div class="modal-chat-empty"><i class="bi bi-chat-text me-2"></i>Sonuç bulunamadı</div>';
945            return;
946        }
947        
948        chatList.innerHTML = chats.map(chat => `
949            <div class="modal-chat-item" data-chat-id="${chat.id}">
950                <div class="modal-chat-item-title" onclick="selectChat(${chat.id})">${chat.title}</div>
951                <button class="modal-chat-delete-btn" onclick="deleteChat(${chat.id}); event.stopPropagation();">
952                    <i class="bi bi-x"></i>
953                </button>
954            </div>
955        `).join('');
956    }
957    
958    // Format date for display
959    function formatDate(dateString) {
960        const date = new Date(dateString + 'T00:00:00');
961        const today = new Date();
962        const yesterday = new Date(today);
963        yesterday.setDate(yesterday.getDate() - 1);
964        
965        if (date.toDateString() === today.toDateString()) {
966            return 'Bugün';
967        } else if (date.toDateString() === yesterday.toDateString()) {
968            return 'Dün';
969        } else {
970            return date.toLocaleDateString('tr-TR', { day: 'numeric', month: 'short' });
971        }
972    }
973    
974    // Initialize chat search functionality
975    function initializeChatSearch() {
976        const searchInput = document.getElementById('modalChatSearch');
977        const clearBtn = document.getElementById('modalSearchClearBtn');
978        
979        if (searchInput) {
980            searchInput.addEventListener('input', function() {
981                filterChats();
982                toggleClearButton();
983            });
984        }
985        
986        if (clearBtn) {
987            clearBtn.addEventListener('click', function() {
988                clearSearch();
989            });
990        }
991    }
992    
993    // Toggle clear button visibility
994    function toggleClearButton() {
995        const searchInput = document.getElementById('modalChatSearch');
996        const clearBtn = document.getElementById('modalSearchClearBtn');
997        
998        if (searchInput && clearBtn) {
999            if (searchInput.value.trim().length > 0) {
1000                clearBtn.style.display = 'flex';
1001            } else {
1002                clearBtn.style.display = 'none';
1003            }
1004        }
1005    }
1006    
1007    // Clear search input and results
1008    function clearSearch() {
1009        const searchInput = document.getElementById('modalChatSearch');
1010        const clearBtn = document.getElementById('modalSearchClearBtn');
1011        
1012        if (searchInput) {
1013            searchInput.value = '';
1014            searchInput.focus();
1015        }
1016        
1017        if (clearBtn) {
1018            clearBtn.style.display = 'none';
1019        }
1020        
1021        // Load all chats from API
1022        loadChats();
1023    }
1024    
1025    // Filter chats based on search term (now uses API search)
1026    function filterChats() {
1027        const searchInput = document.getElementById('modalChatSearch');
1028        if (!searchInput) return;
1029        
1030        const searchTerm = searchInput.value.trim();
1031        
1032        if (!searchTerm) {
1033            // Load all chats if search is empty
1034            loadChats();
1035        } else {
1036            // Use API search
1037            searchChats(searchTerm);
1038        }
1039    }
1040    
1041    // Select and load a chat
1042    async function selectChat(chatId) {
1043        currentConversationId = chatId;
1044        
1045        // Load messages for this conversation
1046        await loadMessages(chatId);
1047        
1048        // Close sidebar to show main chat area
1049            hideModalSidebar();
1050    }
1051            
1052    // Delete (archive) a chat
1053    async function deleteChat(chatId) {
1054        if (confirm('Bu sohbeti silmek istediğinizden emin misiniz?')) {
1055            try {
1056                const response = await fetch(
1057                    `/api/chat/conversation/${chatId}/archive`,
1058                    {
1059                        method: 'POST',
1060                        credentials: 'include',
1061                        headers: {
1062                            'Content-Type': 'application/json'
1063                        }
1064                    }
1065                );
1066                
1067                if (!response.ok) {
1068                    throw new Error(`HTTP ${response.status}`);
1069                }
1070                
1071                // If deleted conversation was current, reset
1072                if (currentConversationId === chatId) {
1073                    currentConversationId = null;
1074                    if (modalMessages) {
1075                        modalMessages.innerHTML = '';
1076                    }
1077                    if (modalEmptyState) {
1078                        modalEmptyState.classList.remove('hidden');
1079                    }
1080                }
1081                
1082                // Reload chat list
1083                loadChats();
1084            } catch (error) {
1085                console.error('Error archiving chat:', error);
1086                alert('Sohbet silinirken bir hata oluştu.');
1087            }
1088        }
1089    }
1090    
1091    // Add message to main chat area
1092    function addMessageToChat(message, sender) {
1093        const messagesContainer = document.querySelector('.modal-chat-messages');
1094        const emptyState = messagesContainer.querySelector('.modal-chat-empty-state');
1095        
1096        // Hide empty state if it exists
1097        if (emptyState) {
1098            emptyState.style.display = 'none';
1099        }
1100        
1101        // Create message element
1102        const messageDiv = document.createElement('div');
1103        messageDiv.className = `chat-message ${sender}-message`;
1104        messageDiv.innerHTML = `
1105            <div class="message-content">
1106                ${message}
1107            </div>
1108            <div class="message-time">
1109                ${new Date().toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' })}
1110            </div>
1111        `;
1112        
1113        messagesContainer.appendChild(messageDiv);
1114        messagesContainer.scrollTop = messagesContainer.scrollHeight;
1115    }
1116    
1117    // Initialize chat functionality when modal opens
1118    function initializeModalChat() {
1119        loadChats(); // Load from API instead of mock
1120        initializeChatSearch();
1121    }
1122    
1123    // Global functions for chat
1124    window.selectChat = selectChat;
1125    window.deleteChat = deleteChat;
1126    
1127    // Initialize chat system
1128    initializeModalChat();
1129    
1130    // Listen for theme changes (observe body class changes)
1131    const themeObserver = new MutationObserver(function(mutations) {
1132        mutations.forEach(function(mutation) {
1133            if (mutation.attributeName === 'class') {
1134                updateModalLogo();
1135            }
1136        });
1137    });
1138    
1139    if (document.body) {
1140        themeObserver.observe(document.body, { attributes: true });
1141    }
1142    
1143    // AIRA MODAL ENHANCEMENTS (Minimize/Restore, Drag, Resize)
1144    // Only affects #modalAssistantOverlay - AiraCoder modal untouched
1145    // ===================================================================
1146    
1147    // Aira Modal Enhancement State
1148    let airaModalState = {
1149        isDragging: false,
1150        dragOffset: { x: 0, y: 0 },
1151        isResizing: false,
1152        resizeDirection: null,
1153        isMinimized: false,
1154        minWidth: 400,
1155        minHeight: 300,
1156        maxWidth: window.innerWidth * 0.9,
1157        maxHeight: window.innerHeight * 0.9
1158    };
1159    
1160    // State persistence functions
1161    function saveAiraModalState(container) {
1162        const rect = container.getBoundingClientRect();
1163        const state = {
1164            x: rect.left,
1165            y: rect.top,
1166            width: rect.width,
1167            height: rect.height,
1168            minimized: airaModalState.isMinimized
1169        };
1170        localStorage.setItem('airaModalState', JSON.stringify(state));
1171    }
1172    
1173    function loadAiraModalState(container) {
1174        try {
1175            const saved = localStorage.getItem('airaModalState');
1176            if (!saved) return;
1177            
1178            const state = JSON.parse(saved);
1179            
1180            // Validate bounds
1181            const maxX = window.innerWidth - state.width;
1182            const maxY = window.innerHeight - state.height;
1183            
1184            const x = Math.max(0, Math.min(state.x, maxX));
1185            const y = Math.max(0, Math.min(state.y, maxY));
1186            const width = Math.max(airaModalState.minWidth, Math.min(state.width, airaModalState.maxWidth));
1187            const height = Math.max(airaModalState.minHeight, Math.min(state.height, airaModalState.maxHeight));
1188            
1189            // Apply saved state
1190            container.style.left = x + 'px';
1191            container.style.top = y + 'px';
1192            container.style.width = width + 'px';
1193            container.style.height = height + 'px';
1194            container.style.right = 'auto';
1195            
1196            // Apply minimized state
1197            if (state.minimized) {
1198                airaModalState.isMinimized = true;
1199                container.classList.add('minimized');
1200                const minimizeBtn = document.getElementById('airaModalMinimizeBtn');
1201                if (minimizeBtn) {
1202                    minimizeBtn.innerHTML = '<i class="bi bi-plus-lg"></i>';
1203                    minimizeBtn.title = 'Büyüt';
1204                }
1205            }
1206        } catch (e) {
1207            console.warn('Failed to load Aira modal state:', e);
1208        }
1209    }
1210    
1211    // Check if mobile device
1212    function isMobileDevice() {
1213        return window.innerWidth <= 768 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1214    }
1215    
1216    // Drag functionality
1217    function initializeAiraModalDrag(airaContainer, header) {
1218        // Disable drag on mobile
1219        if (isMobileDevice()) return;
1220        
1221        // Remove existing drag listeners first
1222        const existingHandler = header._airaModalDragHandler;
1223        if (existingHandler) {
1224            header.removeEventListener('mousedown', existingHandler);
1225        }
1226        
1227        let isDragging = false;
1228        let dragOffset = { x: 0, y: 0 };
1229        
1230        function dragHandler(e) {
1231            // Don't drag if clicking on buttons
1232            if (e.target.closest('button')) return;
1233            
1234            isDragging = true;
1235            const rect = airaContainer.getBoundingClientRect();
1236            dragOffset.x = e.clientX - rect.left;
1237            dragOffset.y = e.clientY - rect.top;
1238            
1239            // Disable transitions during drag for smoothness
1240            airaContainer.style.transition = 'none';
1241            
1242            document.addEventListener('mousemove', handleDrag);
1243            document.addEventListener('mouseup', handleDragEnd);
1244            
1245            // Prevent text selection
1246            e.preventDefault();
1247        }
1248        
1249        let dragAnimationId = null;
1250        
1251        function handleDrag(e) {
1252            if (!isDragging) return;
1253            
1254            // Cancel previous animation frame for smooth movement
1255            if (dragAnimationId) {
1256                cancelAnimationFrame(dragAnimationId);
1257            }
1258            
1259            dragAnimationId = requestAnimationFrame(() => {
1260                let newX = e.clientX - dragOffset.x;
1261                let newY = e.clientY - dragOffset.y;
1262                
1263                // Boundary checks
1264                const maxX = window.innerWidth - airaContainer.offsetWidth;
1265                const maxY = window.innerHeight - airaContainer.offsetHeight;
1266                
1267                newX = Math.max(0, Math.min(newX, maxX));
1268                newY = Math.max(0, Math.min(newY, maxY));
1269                
1270                airaContainer.style.setProperty('position', 'fixed', 'important');
1271                airaContainer.style.setProperty('left', newX + 'px', 'important');
1272                airaContainer.style.setProperty('top', newY + 'px', 'important');
1273                airaContainer.style.setProperty('right', 'auto', 'important');
1274                airaContainer.style.setProperty('transform', 'none', 'important');
1275                airaContainer.style.setProperty('margin', '0', 'important');
1276            });
1277        }
1278        
1279        function handleDragEnd() {
1280            isDragging = false;
1281            document.removeEventListener('mousemove', handleDrag);
1282            document.removeEventListener('mouseup', handleDragEnd);
1283            
1284            // Cancel any pending animation frame
1285            if (dragAnimationId) {
1286                cancelAnimationFrame(dragAnimationId);
1287            }
1288            
1289            // Re-enable transitions after drag for normal animations
1290            airaContainer.style.transition = '';
1291            
1292            // Save state after drag
1293            saveAiraModalState(airaContainer);
1294        }
1295        
1296        // Store handler reference for cleanup
1297        header._airaModalDragHandler = dragHandler;
1298        header.addEventListener('mousedown', dragHandler);
1299    }
1300    
1301    // Resize functionality
1302    function initializeAiraModalResize(airaContainer) {
1303        // Disable resize on mobile
1304        if (isMobileDevice()) return;
1305        
1306        const resizeHandles = airaContainer.querySelectorAll('.aira-resize-handle');
1307        
1308        // Clean up existing listeners
1309        resizeHandles.forEach(handle => {
1310            const existingHandler = handle._airaModalResizeHandler;
1311            if (existingHandler) {
1312                handle.removeEventListener('mousedown', existingHandler);
1313            }
1314        });
1315        
1316        resizeHandles.forEach(handle => {
1317            function resizeHandler(e) {
1318                e.preventDefault();
1319                e.stopPropagation();
1320                
1321                airaModalState.isResizing = true;
1322                airaModalState.resizeDirection = handle.dataset.direction;
1323                
1324                const startX = e.clientX;
1325                const startY = e.clientY;
1326                const startRect = airaContainer.getBoundingClientRect();
1327                
1328                // Disable transitions during resize for smoothness
1329                airaContainer.style.transition = 'none';
1330                
1331                document.addEventListener('mousemove', handleResize);
1332                document.addEventListener('mouseup', handleResizeEnd);
1333                
1334                let rafId = null;
1335                
1336                function handleResize(e) {
1337                    if (!airaModalState.isResizing) return;
1338                    
1339                    // Cancel previous frame for smooth animation
1340                    if (rafId) {
1341                        cancelAnimationFrame(rafId);
1342                    }
1343                    
1344                    rafId = requestAnimationFrame(() => {
1345                        const deltaX = e.clientX - startX;
1346                        const deltaY = e.clientY - startY;
1347                        const direction = airaModalState.resizeDirection;
1348                        
1349                        let newWidth = startRect.width;
1350                        let newHeight = startRect.height;
1351                        let newLeft = startRect.left;
1352                        let newTop = startRect.top;
1353                        
1354                        // Handle different resize directions
1355                        if (direction.includes('e')) {
1356                            newWidth = Math.max(airaModalState.minWidth, 
1357                                      Math.min(airaModalState.maxWidth, startRect.width + deltaX));
1358                        }
1359                        if (direction.includes('w')) {
1360                            newWidth = Math.max(airaModalState.minWidth, 
1361                                      Math.min(airaModalState.maxWidth, startRect.width - deltaX));
1362                            newLeft = startRect.left + (startRect.width - newWidth);
1363                        }
1364                        if (direction.includes('s')) {
1365                            newHeight = Math.max(airaModalState.minHeight, 
1366                                       Math.min(airaModalState.maxHeight, startRect.height + deltaY));
1367                        }
1368                        if (direction.includes('n')) {
1369                            newHeight = Math.max(airaModalState.minHeight, 
1370                                       Math.min(airaModalState.maxHeight, startRect.height - deltaY));
1371                            newTop = startRect.top + (startRect.height - newHeight);
1372                        }
1373                        
1374                        // Apply new dimensions and position with !important
1375                        airaContainer.style.setProperty('width', newWidth + 'px', 'important');
1376                        airaContainer.style.setProperty('height', newHeight + 'px', 'important');
1377                        airaContainer.style.setProperty('left', newLeft + 'px', 'important');
1378                        airaContainer.style.setProperty('top', newTop + 'px', 'important');
1379                        airaContainer.style.setProperty('right', 'auto', 'important');
1380                    });
1381                }
1382                
1383                function handleResizeEnd() {
1384                    airaModalState.isResizing = false;
1385                    airaModalState.resizeDirection = null;
1386                    document.removeEventListener('mousemove', handleResize);
1387                    document.removeEventListener('mouseup', handleResizeEnd);
1388                    
1389                    // Cancel any pending animation frame
1390                    if (rafId) {
1391                        cancelAnimationFrame(rafId);
1392                    }
1393                    
1394                    // Re-enable transitions after resize for normal animations
1395                    airaContainer.style.transition = '';
1396                    
1397                    // Save state after resize
1398                    saveAiraModalState(airaContainer);
1399                }
1400            }
1401            
1402            // Store handler reference for cleanup
1403            handle._airaModalResizeHandler = resizeHandler;
1404            handle.addEventListener('mousedown', resizeHandler);
1405        });
1406    }
1407    
1408    // Enhanced modal functionality - Direct drag/resize
1409    function initializeAiraModalEnhancements() {
1410        const airaModal = document.getElementById('modalAssistantOverlay');
1411        const airaContainer = airaModal?.querySelector('.modal-assistant-container');
1412        const minimizeBtn = document.getElementById('airaModalMinimizeBtn');
1413        const glowToggleBtn = document.getElementById('airaModalGlowToggleBtn');
1414        const header = airaModal?.querySelector('.modal-assistant-header');
1415        
1416        if (!airaModal || !airaContainer || !minimizeBtn || !glowToggleBtn || !header) return;
1417        
1418        // Convert to fixed positioning for drag/resize (keep current position)
1419        const rect = airaContainer.getBoundingClientRect();
1420        airaContainer.style.setProperty('position', 'fixed', 'important');
1421        airaContainer.style.setProperty('top', rect.top + 'px', 'important');
1422        airaContainer.style.setProperty('left', rect.left + 'px', 'important');
1423        airaContainer.style.setProperty('width', rect.width + 'px', 'important');
1424        airaContainer.style.setProperty('height', rect.height + 'px', 'important');
1425        airaContainer.style.setProperty('transform', 'none', 'important');
1426        airaContainer.style.setProperty('margin', '0', 'important');
1427        airaContainer.classList.add('aira-draggable');
1428        // Glow effect CSS'te otomatik uygulanıyor
1429        
1430        // Initialize drag, resize, and glow toggle functionality
1431        initializeAiraModalDrag(airaContainer, header);
1432        initializeAiraModalResize(airaContainer);
1433        initializeAiraModalGlowToggle(airaContainer, glowToggleBtn);
1434        
1435        // Store original position/size before minimize
1436        let originalState = null;
1437        
1438        // Minimize/Restore toggle with smooth animation
1439        minimizeBtn.addEventListener('click', function(e) {
1440            e.stopPropagation(); // Prevent header drag
1441            
1442            airaModalState.isMinimized = !airaModalState.isMinimized;
1443            
1444            if (airaModalState.isMinimized) {
1445                // Save current position/size before minimizing
1446                const rect = airaContainer.getBoundingClientRect();
1447                originalState = {
1448                    width: rect.width,
1449                    height: rect.height,
1450                    top: rect.top,
1451                    left: rect.left
1452                };
1453                
1454                // Smooth minimize animation
1455                animateMinimize(airaContainer, originalState);
1456                
1457                minimizeBtn.innerHTML = '<i class="bi bi-plus-lg"></i>';
1458                minimizeBtn.title = ''; // Remove tooltip when minimized
1459            } else {
1460                // Smooth restore animation
1461                if (originalState) {
1462                    animateRestore(airaContainer, originalState);
1463                }
1464                
1465                minimizeBtn.innerHTML = '<i class="bi bi-dash-lg"></i>';
1466                minimizeBtn.title = 'Küçült';
1467            }
1468            
1469            // Save state after minimize/restore
1470            saveAiraModalState(airaContainer);
1471        });
1472        
1473        // Add click event to modal container for restore when minimized
1474        airaContainer.addEventListener('click', function(e) {
1475            // Only restore if minimized (header is hidden anyway)
1476            if (airaModalState.isMinimized) {
1477                // Trigger restore by simulating minimize button click
1478                minimizeBtn.click();
1479            }
1480        });
1481    }
1482    
1483    // Smooth minimize animation
1484    function animateMinimize(container, fromState) {
1485        // Add transition temporarily
1486        container.style.transition = 'all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1)';
1487        
1488        // Apply minimized styles
1489        requestAnimationFrame(() => {
1490            container.style.setProperty('width', '100px', 'important');
1491            container.style.setProperty('height', '100px', 'important');
1492            container.style.setProperty('bottom', '20px', 'important');
1493            container.style.setProperty('right', '20px', 'important');
1494            container.style.setProperty('top', 'auto', 'important');
1495            container.style.setProperty('left', 'auto', 'important');
1496            container.style.setProperty('overflow', 'hidden', 'important');
1497            
1498            container.classList.add('minimized');
1499        });
1500        
1501        // Remove transition after animation
1502        setTimeout(() => {
1503            container.style.transition = '';
1504        }, 500);
1505    }
1506    
1507    // Smooth restore animation  
1508    function animateRestore(container, toState) {
1509        // Add transition temporarily
1510        container.style.transition = 'all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)';
1511        
1512        // Apply restored styles
1513        requestAnimationFrame(() => {
1514            container.style.setProperty('width', toState.width + 'px', 'important');
1515            container.style.setProperty('height', toState.height + 'px', 'important');
1516            container.style.setProperty('top', toState.top + 'px', 'important');
1517            container.style.setProperty('left', toState.left + 'px', 'important');
1518            container.style.setProperty('bottom', 'auto', 'important');
1519            container.style.setProperty('right', 'auto', 'important');
1520            container.style.setProperty('overflow', 'visible', 'important');
1521            
1522            container.classList.remove('minimized');
1523        });
1524        
1525        // Remove transition after animation
1526        setTimeout(() => {
1527            container.style.transition = '';
1528        }, 400);
1529    }
1530    
1531    // Glow toggle functionality
1532    function initializeAiraModalGlowToggle(container, glowToggleBtn) {
1533        let isGlowEnabled = true; // Default: glow is enabled
1534        
1535        // Set initial button state
1536        updateGlowToggleButton(glowToggleBtn, isGlowEnabled);
1537        
1538        glowToggleBtn.addEventListener('click', function(e) {
1539            e.stopPropagation(); // Prevent header drag
1540            
1541            isGlowEnabled = !isGlowEnabled;
1542            
1543            if (isGlowEnabled) {
1544                // Enable glow effect
1545                container.style.filter = '';
1546                container.style.animation = '';
1547                container.classList.remove('glow-disabled');
1548            } else {
1549                // Disable glow effect
1550                container.style.filter = 'none';
1551                container.style.animation = 'none';
1552                container.classList.add('glow-disabled');
1553            }
1554            
1555            // Update button appearance
1556            updateGlowToggleButton(glowToggleBtn, isGlowEnabled);
1557            
1558            // Save state to localStorage
1559            localStorage.setItem('airaModalGlowEnabled', isGlowEnabled.toString());
1560        });
1561        
1562        // Load saved state
1563        const savedGlowState = localStorage.getItem('airaModalGlowEnabled');
1564        if (savedGlowState !== null) {
1565            isGlowEnabled = savedGlowState === 'true';
1566            
1567            if (!isGlowEnabled) {
1568                container.style.filter = 'none';
1569                container.style.animation = 'none';
1570                container.classList.add('glow-disabled');
1571            }
1572            
1573            updateGlowToggleButton(glowToggleBtn, isGlowEnabled);
1574        }
1575    }
1576    
1577    // Update glow toggle button appearance
1578    function updateGlowToggleButton(button, isEnabled) {
1579        const icon = button.querySelector('i');
1580        if (isEnabled) {
1581            icon.className = 'bi bi-lightning-charge'; // Glow açık = normal ikon
1582            button.classList.remove('active');
1583            button.title = ''; // Remove tooltip
1584        } else {
1585            icon.className = 'bi bi-lightning-charge-fill'; // Glow kapalı = fill ikon + glow
1586            button.classList.add('active');
1587            button.title = ''; // Remove tooltip
1588        }
1589    }
1590    
1591    // Initialize enhancements when modal is opened
1592    // Use MutationObserver to detect when modal is shown
1593    function observeModalOpen() {
1594        const airaModal = document.getElementById('modalAssistantOverlay');
1595        if (!airaModal) return;
1596        
1597        const observer = new MutationObserver(function(mutations) {
1598            mutations.forEach(function(mutation) {
1599                if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
1600                    const target = mutation.target;
1601                    if (target.classList.contains('show')) {
1602                        // Modal opened, initialize enhancements
1603                        setTimeout(initializeAiraModalEnhancements, 100);
1604                    }
1605                }
1606            });
1607        });
1608        
1609        observer.observe(airaModal, {
1610            attributes: true,
1611            attributeFilter: ['class']
1612        });
1613    }
1614    
1615    // Initialize observer when DOM is ready
1616    if (document.readyState === 'loading') {
1617        document.addEventListener('DOMContentLoaded', observeModalOpen);
1618    } else {
1619        observeModalOpen();
1620    }
1621})();
1622

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.