File size: 7,488 Bytes
25ac863
 
 
 
 
 
 
 
 
 
 
 
 
d510348
25ac863
 
 
 
d510348
 
25ac863
 
 
 
 
 
 
d510348
 
25ac863
 
 
 
 
 
 
 
d510348
25ac863
 
 
 
 
 
 
d510348
25ac863
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d510348
 
25ac863
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5874c3
25ac863
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
document.addEventListener('DOMContentLoaded', () => {
    const chatForm = document.getElementById('chat-form');
    const promptInput = document.getElementById('prompt');
    const chatMessages = document.getElementById('chat-messages');
    const loadingIndicator = document.getElementById('loading-indicator');
    const errorMessageDiv = document.getElementById('error-message');
    const webSearchToggle = document.getElementById('web_search_toggle');
    const fileUpload = document.getElementById('file_upload');
    const fileNameSpan = document.getElementById('file-name');
    const sendButton = document.getElementById('send-button');

    // --- Fonctions Utilitaires ---

    function scrollToBottom() {
        // Ajoute un léger délai pour laisser le temps au DOM de se mettre à jour
        setTimeout(() => {
            chatMessages.scrollTop = chatMessages.scrollHeight;
        }, 50);
    }

    function showLoading(show) {
        loadingIndicator.style.display = show ? 'block' : 'none';
        sendButton.disabled = show;
         promptInput.disabled = show;
        // Optionnel: changer l'apparence du bouton pendant le chargement
        sendButton.classList.toggle('opacity-50', show);
         sendButton.classList.toggle('cursor-not-allowed', show);
    }

    function displayError(message) {
        errorMessageDiv.textContent = message;
        errorMessageDiv.style.display = 'block';
        // Cache le message après quelques secondes
        setTimeout(() => {
            errorMessageDiv.style.display = 'none';
        }, 5000);
    }

    function addMessageToChat(role, text) {
         // Assainir le texte avant de l'insérer (très basique, pour éviter XSS simple)
        // Pour du Markdown, une bibliothèque comme 'marked' ou 'showdown' serait nécessaire côté client
        // ou s'assurer que le backend renvoie du HTML sûr.
        // Ici, on suppose que le backend renvoie du texte simple ou du HTML déjà aseptisé avec | safe
        const messageDiv = document.createElement('div');
        messageDiv.classList.add('flex', role === 'user' ? 'justify-end' : 'justify-start');

        const bubbleDiv = document.createElement('div');
         bubbleDiv.classList.add('p-3', 'rounded-lg', 'max-w-xs', 'md:max-w-md', 'shadow');
         if (role === 'user') {
             bubbleDiv.classList.add('bg-blue-500', 'text-white', 'rounded-br-none');
         } else {
             // Pour l'assistant, créer une div interne pour le formatage prose si nécessaire
            bubbleDiv.classList.add('bg-gray-200', 'text-gray-800', 'rounded-bl-none');
             const proseDiv = document.createElement('div');
             proseDiv.classList.add('prose', 'prose-sm', 'max-w-none');
             // IMPORTANT: Si le backend renvoie du HTML, il DOIT être sûr.
             // Sinon, utiliser textContent pour éviter l'injection de script.
             proseDiv.innerHTML = text; // Suppose que le HTML reçu est sûr (ex: via Markdown rendu côté serveur)
             // Si juste du texte : proseDiv.textContent = text;
             bubbleDiv.appendChild(proseDiv);
         }

         // Si c'est un message utilisateur simple (pas de HTML)
         if(role === 'user'){
             const paragraph = document.createElement('p');
             paragraph.classList.add('text-sm');
             paragraph.textContent = text; // Utiliser textContent pour la sécurité
             bubbleDiv.appendChild(paragraph);
         }


        messageDiv.appendChild(bubbleDiv);
        chatMessages.appendChild(messageDiv);

        // Insérer l'indicateur *après* le dernier message pour qu'il soit en bas
        chatMessages.appendChild(loadingIndicator);

        scrollToBottom();
    }

    // --- Gestionnaires d'événements ---

    // Afficher le nom du fichier sélectionné
    fileUpload.addEventListener('change', () => {
        if (fileUpload.files.length > 0) {
            fileNameSpan.textContent = fileUpload.files[0].name;
            fileNameSpan.title = fileUpload.files[0].name; // Pour le nom complet au survol
        } else {
            fileNameSpan.textContent = '';
             fileNameSpan.title = '';
        }
    });

    // Soumission du formulaire via Fetch API
    chatForm.addEventListener('submit', async (e) => {
        e.preventDefault(); // Empêche le rechargement de la page

        const prompt = promptInput.value.trim();
        const file = fileUpload.files[0];
        const useWebSearch = webSearchToggle.checked;

        if (!prompt && !file) {
            displayError("Veuillez entrer un message ou sélectionner un fichier.");
            return;
        }

        // Cacher les erreurs précédentes
        errorMessageDiv.style.display = 'none';

        // Afficher le message utilisateur immédiatement
         let userMessageText = prompt;
         if (file) {
             userMessageText = `[Fichier: ${file.name}]\n\n${prompt}`;
         }
        addMessageToChat('user', userMessageText);

        // Préparer les données du formulaire pour l'envoi (y compris le fichier)
        const formData = new FormData();
        formData.append('prompt', prompt);
        formData.append('web_search', useWebSearch);
        if (file) {
            formData.append('file', file);
        }

        // Afficher le chargement et désactiver les entrées
        showLoading(true);
        promptInput.value = ''; // Vider l'input après l'envoi
        fileUpload.value = ''; // Réinitialiser l'input fichier
        fileNameSpan.textContent = ''; // Vider le nom du fichier affiché

        try {
            const response = await fetch("{{ url_for('chat_api') }}", { // Appel vers la nouvelle route API
                method: 'POST',
                body: formData, // Pas besoin de 'Content-Type', le navigateur le définit pour FormData
            });

            if (!response.ok) {
                // Essayer de lire l'erreur JSON si possible
                let errorMsg = `Erreur HTTP: ${response.status}`;
                 try {
                    const errorData = await response.json();
                    errorMsg = errorData.error || errorMsg;
                 } catch (jsonError) {
                     // Ignorer si la réponse n'est pas du JSON
                 }
                throw new Error(errorMsg);
            }

            const data = await response.json();

            if (data.success && data.message) {
                 // IMPORTANT: S'assurer que data.message est du HTML sûr ou l'assainir ici
                addMessageToChat('assistant', data.message);
            } else if (data.error) {
                displayError(data.error);
                 // Optionnel: supprimer le message utilisateur si l'IA n'a pas pu répondre
                 // (peut être déroutant pour l'utilisateur)
            }

        } catch (error) {
            console.error("Erreur lors de l'envoi du message:", error);
            displayError(error.message || "Une erreur inconnue est survenue.");
             // Optionnel: supprimer le message utilisateur en cas d'erreur réseau grave
        } finally {
            // Cacher le chargement et réactiver les entrées
            showLoading(false);
            promptInput.focus(); // Remettre le focus sur l'input
        }
    });

    // Scroll initial vers le bas au chargement de la page
    scrollToBottom();
    promptInput.focus(); // Focus sur l'input au chargement
});