Docfile commited on
Commit
db67e3f
·
verified ·
1 Parent(s): 306906f

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +302 -0
templates/index.html ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mariam M-0</title>
7
+
8
+ <!-- Intégration de Tailwind CSS -->
9
+ <script defer src="https://cdn.tailwindcss.com"></script>
10
+
11
+ <!-- Marked pour le rendu Markdown -->
12
+ <script defer src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
13
+
14
+ <!-- DOMPurify pour sécuriser le rendu HTML -->
15
+ <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script>
16
+
17
+ <!-- Highlight.js pour la coloration syntaxique -->
18
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
19
+ <script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
20
+
21
+ <style>
22
+ @keyframes gradient {
23
+ 0% { background-position: 0% 50%; }
24
+ 50% { background-position: 100% 50%; }
25
+ 100% { background-position: 0% 50%; }
26
+ }
27
+ .gradient-bg {
28
+ background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
29
+ background-size: 400% 400%;
30
+ animation: gradient 15s ease infinite;
31
+ }
32
+
33
+ /* Assurer la bonne gestion des espaces et retours à la ligne sur mobile */
34
+ .markdown-content {
35
+ white-space: pre-wrap;
36
+ word-break: break-word;
37
+ }
38
+
39
+ /* Styles pour le rendu Markdown en CSS natif */
40
+ .markdown-content h1 {
41
+ font-size: 1.5rem; /* text-2xl */
42
+ font-weight: 700; /* font-bold */
43
+ margin-top: 1.5rem; /* mt-6 */
44
+ margin-bottom: 1rem; /* mb-4 */
45
+ }
46
+ .markdown-content h2 {
47
+ font-size: 1.25rem; /* text-xl */
48
+ font-weight: 700;
49
+ margin-top: 1.25rem; /* mt-5 */
50
+ margin-bottom: 0.75rem; /* mb-3 */
51
+ }
52
+ .markdown-content h3 {
53
+ font-size: 1.125rem; /* text-lg */
54
+ font-weight: 700;
55
+ margin-top: 1rem; /* mt-4 */
56
+ margin-bottom: 0.5rem; /* mb-2 */
57
+ }
58
+ .markdown-content p {
59
+ margin-bottom: 1rem; /* mb-4 */
60
+ line-height: 1.625; /* leading-relaxed */
61
+ }
62
+ .markdown-content ul {
63
+ list-style-type: disc;
64
+ margin-left: 1.5rem; /* ml-6 */
65
+ margin-bottom: 1rem; /* mb-4 */
66
+ }
67
+ .markdown-content ol {
68
+ list-style-type: decimal;
69
+ margin-left: 1.5rem;
70
+ margin-bottom: 1rem;
71
+ }
72
+ .markdown-content li {
73
+ margin-bottom: 0.25rem; /* mb-1 */
74
+ }
75
+ .markdown-content a {
76
+ color: #2563eb; /* text-blue-600 */
77
+ text-decoration: underline;
78
+ }
79
+ .markdown-content a:hover {
80
+ color: #1d4ed8; /* text-blue-800 */
81
+ }
82
+ .markdown-content blockquote {
83
+ padding-left: 1rem; /* pl-4 */
84
+ border-left: 4px solid #D1D5DB; /* border-gray-300 */
85
+ font-style: italic;
86
+ margin: 1rem 0; /* my-4 */
87
+ }
88
+ .markdown-content code:not(pre code) {
89
+ background-color: #F3F4F6; /* bg-gray-100 */
90
+ padding: 0 0.25rem; /* px-1 */
91
+ border-radius: 0.25rem; /* rounded */
92
+ font-size: 0.875rem; /* text-sm */
93
+ font-family: monospace;
94
+ }
95
+ .markdown-content pre {
96
+ background-color: #F3F4F6;
97
+ padding: 1rem; /* p-4 */
98
+ border-radius: 0.5rem; /* rounded-lg */
99
+ margin-bottom: 1rem; /* mb-4 */
100
+ overflow-x: auto;
101
+ }
102
+ .markdown-content table {
103
+ width: 100%;
104
+ border: 1px solid #D1D5DB; /* border-gray-300 */
105
+ margin-bottom: 1rem;
106
+ border-collapse: collapse;
107
+ }
108
+ .markdown-content th,
109
+ .markdown-content td {
110
+ border-bottom: 1px solid #D1D5DB;
111
+ padding: 0.5rem 1rem; /* px-4 py-2 */
112
+ text-align: left;
113
+ }
114
+ .markdown-content th {
115
+ background-color: #F3F4F6;
116
+ }
117
+ .markdown-content img {
118
+ max-width: 100%;
119
+ height: auto;
120
+ margin: 1rem 0;
121
+ border-radius: 0.5rem;
122
+ }
123
+ </style>
124
+ </head>
125
+ <body class="min-h-screen bg-gray-50">
126
+ <!-- Barre de gradient fixe en haut de page -->
127
+ <div class="gradient-bg h-2 w-full fixed top-0"></div>
128
+
129
+ <div class="max-w-4xl mx-auto px-4 py-8">
130
+ <header class="text-center mb-12">
131
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">Mariam M-0</h1>
132
+ <p class="text-gray-600">Votre assistant IA personnel</p>
133
+ </header>
134
+
135
+ <main>
136
+ <section class="bg-white rounded-xl shadow-lg p-6 mb-8">
137
+ <textarea
138
+ id="question"
139
+ class="w-full h-32 p-4 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
140
+ placeholder="Posez votre question ici..."
141
+ aria-label="Zone de saisie de la question"
142
+ ></textarea>
143
+ <button
144
+ id="submitBtn"
145
+ class="mt-4 px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-lg hover:from-blue-600 hover:to-blue-700 transition-all duration-200 flex items-center justify-center w-full sm:w-auto"
146
+ aria-label="Obtenir une réponse"
147
+ >
148
+ <span>Obtenir une réponse</span>
149
+ <div id="spinner" class="hidden ml-3 animate-spin rounded-full h-5 w-5 border-b-2 border-white" aria-hidden="true"></div>
150
+ </button>
151
+ </section>
152
+
153
+ <section id="responseContainer" class="space-y-6">
154
+ <article id="answerBox" class="hidden bg-white rounded-xl shadow-lg p-6 relative">
155
+ <div class="flex items-center justify-between mb-4">
156
+ <h2 class="text-xl font-semibold text-gray-800">Réponse</h2>
157
+ <!-- Bouton Copier -->
158
+ <button
159
+ id="copyBtn"
160
+ class="px-3 py-1 bg-blue-500 text-white text-sm rounded hover:bg-blue-600 transition-colors"
161
+ title="Copier la réponse"
162
+ aria-label="Copier la réponse"
163
+ >
164
+ Copier
165
+ </button>
166
+ </div>
167
+ <div id="answer" class="markdown-content text-gray-700"></div>
168
+ </article>
169
+
170
+ <article id="thinkingBox" class="hidden bg-white rounded-xl shadow-lg p-6">
171
+ <div class="flex items-center justify-between mb-4">
172
+ <h2 class="text-xl font-semibold text-gray-800">Raisonnement</h2>
173
+ <button
174
+ id="toggleThinking"
175
+ class="text-blue-500 hover:text-blue-600 focus:outline-none"
176
+ aria-expanded="false"
177
+ aria-controls="thinking"
178
+ >
179
+ Afficher
180
+ </button>
181
+ </div>
182
+ <div id="thinking" class="hidden markdown-content text-gray-600"></div>
183
+ </article>
184
+ </section>
185
+ </main>
186
+ </div>
187
+
188
+ <script defer>
189
+ // Configuration de marked pour le rendu Markdown
190
+ marked.setOptions({
191
+ highlight: function(code, lang) {
192
+ if (lang && hljs.getLanguage(lang)) {
193
+ return hljs.highlight(code, { language: lang }).value;
194
+ }
195
+ return hljs.highlightAuto(code).value;
196
+ },
197
+ breaks: true,
198
+ gfm: true
199
+ });
200
+
201
+ /**
202
+ * Fonction pour rendre le Markdown de manière sécurisée.
203
+ * @param {string} content - Le contenu en Markdown.
204
+ * @returns {string} Le HTML rendu et sécurisé.
205
+ */
206
+ function renderMarkdown(content) {
207
+ const rawHtml = marked.parse(content);
208
+ return DOMPurify.sanitize(rawHtml);
209
+ }
210
+
211
+ const submitBtn = document.getElementById('submitBtn');
212
+ const spinner = document.getElementById('spinner');
213
+ const answerBox = document.getElementById('answerBox');
214
+ const thinkingBox = document.getElementById('thinkingBox');
215
+ const answer = document.getElementById('answer');
216
+ const thinking = document.getElementById('thinking');
217
+ const toggleThinking = document.getElementById('toggleThinking');
218
+ const copyBtn = document.getElementById('copyBtn');
219
+
220
+ // Bouton pour basculer l'affichage du raisonnement
221
+ toggleThinking.addEventListener('click', () => {
222
+ const isHidden = thinking.classList.contains('hidden');
223
+ thinking.classList.toggle('hidden');
224
+ toggleThinking.textContent = isHidden ? 'Masquer' : 'Afficher';
225
+ toggleThinking.setAttribute('aria-expanded', isHidden);
226
+ });
227
+
228
+ // Bouton Copier
229
+ copyBtn.addEventListener('click', () => {
230
+ // Crée un élément temporaire pour copier le texte brut (sans HTML)
231
+ const tempElement = document.createElement('textarea');
232
+ tempElement.value = answer.innerText;
233
+ document.body.appendChild(tempElement);
234
+ tempElement.select();
235
+ try {
236
+ document.execCommand('copy');
237
+ copyBtn.textContent = 'Copié';
238
+ setTimeout(() => {
239
+ copyBtn.textContent = 'Copier';
240
+ }, 1500);
241
+ } catch (err) {
242
+ console.error('Erreur lors de la copie :', err);
243
+ }
244
+ document.body.removeChild(tempElement);
245
+ });
246
+
247
+ // Gestion de la soumission de la question
248
+ submitBtn.addEventListener('click', async () => {
249
+ const question = document.getElementById('question').value;
250
+ if (!question.trim()) return;
251
+
252
+ // Réinitialisation de l'interface utilisateur
253
+ answer.innerHTML = '';
254
+ thinking.innerHTML = '';
255
+ submitBtn.disabled = true;
256
+ spinner.classList.remove('hidden');
257
+ answerBox.classList.add('hidden');
258
+ thinkingBox.classList.add('hidden');
259
+
260
+ try {
261
+ const response = await fetch('/ask', {
262
+ method: 'POST',
263
+ headers: { 'Content-Type': 'application/json' },
264
+ body: JSON.stringify({ question })
265
+ });
266
+
267
+ // Lecture du flux de réponse en continu
268
+ const reader = response.body.getReader();
269
+ const decoder = new TextDecoder();
270
+
271
+ while (true) {
272
+ const { done, value } = await reader.read();
273
+ if (done) break;
274
+ const chunks = decoder.decode(value).split('\n');
275
+ chunks.forEach(chunk => {
276
+ if (!chunk) return;
277
+ const data = JSON.parse(chunk);
278
+ if (data.type === 'thinking') {
279
+ thinkingBox.classList.remove('hidden');
280
+ thinking.innerHTML = renderMarkdown(data.content);
281
+ } else if (data.type === 'answer') {
282
+ answerBox.classList.remove('hidden');
283
+ answer.innerHTML = renderMarkdown(data.content);
284
+ // Rafraîchissement de la coloration syntaxique pour les blocs de code
285
+ answer.querySelectorAll('pre code').forEach(block => {
286
+ hljs.highlightElement(block);
287
+ });
288
+ }
289
+ });
290
+ }
291
+ } catch (error) {
292
+ console.error('Error:', error);
293
+ answer.innerHTML = renderMarkdown("❌ Une erreur est survenue. Veuillez réessayer.");
294
+ answerBox.classList.remove('hidden');
295
+ } finally {
296
+ submitBtn.disabled = false;
297
+ spinner.classList.add('hidden');
298
+ }
299
+ });
300
+ </script>
301
+ </body>
302
+ </html>