Docfile commited on
Commit
d083d33
·
verified ·
1 Parent(s): 81b0eb8

Update templates/philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +219 -193
templates/philosophie.html CHANGED
@@ -1,9 +1,9 @@
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>Assistant de Philosophie (Vue.js)</title>
7
 
8
  <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
@@ -13,182 +13,99 @@
13
  <link href="https://fonts.googleapis.com/css2?family=Kalam&family=Lato:wght@400;700&display=swap" rel="stylesheet">
14
 
15
  <style>
16
- :root {
17
- --primary-color: #4f46e5;
18
- --primary-hover: #4338ca;
19
- --text-color: #374151;
20
- --background-color: #f8f9fa;
21
- --container-bg: #ffffff;
22
- --border-color: #e5e7eb;
23
- }
24
-
25
- body {
26
- font-family: 'Lato', sans-serif;
27
- background-color: var(--background-color);
28
- margin: 0;
29
- padding: 2rem;
30
- color: var(--text-color);
31
- -webkit-font-smoothing: antialiased;
32
- -moz-osx-font-smoothing: grayscale;
33
- }
34
-
35
- .container {
36
- max-width: 960px;
37
- margin: 0 auto;
38
- background: var(--container-bg);
39
- padding: 40px;
40
- border-radius: 12px;
41
- box-shadow: 0 6px 20px rgba(0,0,0,0.08);
42
- }
43
-
44
- h1 { text-align: center; color: #111827; margin-bottom: 0.5em; }
45
- .type-indicator { text-align: center; color: #6b7280; font-size: 1.1em; margin-bottom: 2em; }
46
-
47
- textarea {
48
- width: 100%;
49
- padding: 15px;
50
- border-radius: 8px;
51
- border: 1px solid var(--border-color);
52
- min-height: 100px;
53
- margin-bottom: 15px;
54
- font-size: 16px;
55
- line-height: 1.6;
56
- transition: border-color 0.3s, box-shadow 0.3s;
57
- }
58
-
59
- textarea:focus {
60
- outline: none;
61
- border-color: var(--primary-color);
62
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2);
63
- }
64
-
65
- button {
66
- display: block;
67
- width: 100%;
68
- padding: 16px;
69
- background-color: var(--primary-color);
70
- color: white;
71
- border: none;
72
- border-radius: 8px;
73
- font-size: 18px;
74
- font-weight: 700;
75
- cursor: pointer;
76
- transition: background-color 0.3s, transform 0.2s;
77
- }
78
-
79
- button:hover:not(:disabled) {
80
- background-color: var(--primary-hover);
81
- transform: translateY(-2px);
82
- }
83
-
84
- button:disabled {
85
- background-color: #9ca3af;
86
- cursor: not-allowed;
87
- }
88
-
89
- .loader {
90
- display: block;
91
- border: 8px solid #f3f3f3;
92
- border-top: 8px solid var(--primary-color);
93
- border-radius: 50%;
94
- width: 50px;
95
- height: 50px;
96
- animation: spin 1s linear infinite;
97
- margin: 30px auto;
98
- }
99
- @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
100
-
101
- .error {
102
- color: #ef4444;
103
- background-color: #fee2e2;
104
- text-align: center;
105
- margin-top: 20px;
106
- padding: 15px;
107
- border-radius: 8px;
108
- font-weight: bold;
109
- }
110
-
111
- /* --- NOUVEAU : Style pour cacher le contenu --- */
112
- /* On le rend, mais on le place hors de l'écran pour que html2pdf puisse le lire */
113
- .hidden-for-pdf {
114
- position: absolute;
115
- left: -9999px;
116
- top: -9999px;
117
- width: 800px; /* Largeur fixe pour aider au rendu PDF */
118
- }
119
-
120
- /* Styles de la feuille de dissertation (conservés pour le rendu PDF) */
121
- .dissertation-paper {
122
  font-family: 'Kalam', cursive;
123
- font-size: 20px;
124
- color: #1a2a4c;
125
- background-color: #fdfaf4;
126
- line-height: 2;
127
  background-image: linear-gradient(transparent 97%, #d8e2ee 98%);
128
- background-size: 100% 40px;
129
- border-left: 3px solid #ffaaab;
130
- padding: 30px 30px 40px 4em;
131
- -webkit-print-color-adjust: exact;
132
- print-color-adjust: exact;
133
  }
134
- .dissertation-paper h2 { font-size: 1.5em; text-align: center; margin-bottom: 1.5em; }
135
- .dissertation-paper h3 { font-size: 1.2em; margin-top: 3em; margin-bottom: 1.5em; text-transform: uppercase; text-decoration: underline; }
136
- .dissertation-paper p { text-align: justify; margin: 0; padding: 0; }
137
- .dissertation-paper .prof { text-align: center; font-style: italic; margin-bottom: 2em; }
138
- .dissertation-paper .indented { text-indent: 3em; }
139
- .dissertation-paper .transition { margin: 2em 0; font-style: italic; color: #4a6a9c; }
140
- .avoid-page-break { page-break-inside: avoid; break-inside: avoid; }
141
  </style>
142
  </head>
143
  <body>
144
  <div id="app" class="container">
145
- <h1>Assistant de Dissertation Philosophique</h1>
146
- <p class="type-indicator">Type 1</p>
147
 
148
- <!-- Le formulaire appelle maintenant la nouvelle méthode -->
149
- <form @submit.prevent="generateAndDownloadPDF">
150
- <textarea v-model="question" placeholder="Entrez votre sujet de dissertation ici..."></textarea>
151
- <button type="submit" :disabled="isLoading">
152
- [[ isLoading ? 'Génération en cours...' : 'Générer et Télécharger en PDF' ]]
153
- </button>
154
- </form>
155
 
156
- <div v-if="isLoading" class="loader"></div>
157
- <p v-if="errorMessage" class="error">[[ errorMessage ]]</p>
 
 
 
158
 
159
- <!-- CE BLOC SERA RENDU MAIS RESTERA INVISIBLE POUR L'UTILISATEUR -->
160
- <div v-if="dissertation" id="dissertation-content" class="dissertation-paper hidden-for-pdf">
161
- <h2>Sujet : [[ dissertation.sujet ]]</h2>
162
- <p class="prof">Prof : [[ dissertation.prof ]]</p>
163
- <h3>Introduction</h3>
164
- <p class="indented">[[ dissertation.introduction ]]</p>
165
- <div v-for="partie in dissertation.parties" :key="partie.chapeau" class="avoid-page-break">
166
- <div>
167
- <p class="indented">[[ partie.chapeau ]]</p>
168
- <p v-for="(arg, idx) in partie.arguments" :key="idx" class="indented">
169
- [[ arg.paragraphe_argumentatif ]]
170
- </p>
171
- </div>
172
- <p v-if="partie.transition" class="indented transition">[[ partie.transition ]]</p>
173
- </div>
174
- <h3>Conclusion</h3>
175
- <p class="indented">[[ dissertation.conclusion ]]</p>
176
- </div>
177
  </div>
178
 
179
  <script>
180
  const { createApp } = Vue;
181
 
182
- const app = createApp({
183
  data() {
184
  return {
185
  question: '',
186
  isLoading: false,
187
- errorMessage: null,
188
- dissertation: null // Toujours nécessaire pour stocker les données temporairement
189
  }
190
  },
191
  methods: {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  async generateAndDownloadPDF() {
193
  if (!this.question.trim()) {
194
  this.errorMessage = "Veuillez entrer un sujet de dissertation.";
@@ -196,63 +113,172 @@
196
  }
197
  this.isLoading = true;
198
  this.errorMessage = null;
199
- this.dissertation = null;
200
 
201
  try {
202
- // 1. Récupérer les données de la dissertation
203
- const response = await fetch('/api/generate_dissertation', {
204
  method: 'POST',
205
  headers: { 'Content-Type': 'application/json' },
206
  body: JSON.stringify({ question: this.question })
207
  });
208
- const data = await response.json();
209
- if (!response.ok) {
210
- throw new Error(data.error || "Une erreur inconnue est survenue.");
211
- }
212
- this.dissertation = data;
213
-
214
- // 2. Attendre que Vue mette à jour le DOM avec le contenu caché
215
- await this.$nextTick();
216
-
217
- // 3. Générer le PDF à partir du contenu maintenant présent (mais caché)
218
- await this.generatePDF();
219
 
220
- } catch (error) {
221
- this.errorMessage = error.message;
222
- } finally {
223
- this.isLoading = false;
224
- // 4. (Optionnel) Nettoyer les données pour retirer l'élément caché du DOM
225
- this.dissertation = null;
226
- }
227
- },
228
 
229
- async generatePDF() {
230
- const element = document.getElementById('dissertation-content');
231
- if (!element) return; // Pas d'erreur visible si l'élément est introuvable
 
 
 
 
 
 
 
 
 
 
232
 
233
- try {
234
- await document.fonts.ready;
235
- window.scrollTo(0, 0);
 
236
 
 
237
  const options = {
238
- margin: [15, 15, 15, 15],
239
  filename: 'dissertation-philosophie.pdf',
240
  image: { type: 'jpeg', quality: 0.98 },
241
- html2canvas: { scale: 2, useCORS: true, backgroundColor: null },
242
  jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
243
  };
244
 
245
- await html2pdf().set(options).from(element).save();
 
 
 
 
246
  } catch (err) {
247
- console.error('Erreur html2pdf:', err);
248
- this.errorMessage = "Erreur lors de la génération du PDF. Voir la console.";
 
 
249
  }
250
  }
251
  }
252
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
- app.config.compilerOptions.delimiters = ['[[', ']]'];
255
- app.mount('#app');
256
  </script>
257
  </body>
258
  </html>
 
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" />
6
+ <title>Assistant - Télécharger PDF sans afficher</title>
7
 
8
  <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
 
13
  <link href="https://fonts.googleapis.com/css2?family=Kalam&family=Lato:wght@400;700&display=swap" rel="stylesheet">
14
 
15
  <style>
16
+ body { font-family: 'Lato', sans-serif; margin: 20px; background:#f4f4f9; color:#333; }
17
+ .container { max-width:700px; margin:0 auto; background:white; padding:20px; border-radius:8px; box-shadow:0 6px 18px rgba(0,0,0,0.06); }
18
+ textarea { width:100%; min-height:80px; padding:10px; margin-bottom:10px; font-size:15px; }
19
+ button { width:100%; padding:12px; font-size:16px; background:#2980b9; color:white; border:0; border-radius:6px; cursor:pointer; }
20
+ button:disabled { background:#95a5a6; cursor:not-allowed; }
21
+ .loader { margin:12px auto; border:6px solid #eee; border-top:6px solid #2980b9; border-radius:50%; width:36px; height:36px; animation:spin 1s linear infinite; }
22
+ @keyframes spin { to { transform:rotate(360deg);} }
23
+ .dissertation-paper { /* styles utilisés pour le rendu PDF (réutilisés dans l'élément hors-écran) */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  font-family: 'Kalam', cursive;
25
+ font-size:18px;
26
+ color:#1a2a4c;
27
+ background-color:#fdfaf4;
28
+ line-height:1.6;
29
  background-image: linear-gradient(transparent 97%, #d8e2ee 98%);
30
+ background-size:100% 36px;
31
+ border-left:2px solid #ffaaab;
32
+ padding:28px;
33
+ box-sizing:border-box;
 
34
  }
35
+ .dissertation-paper h2 { text-align:center; margin-bottom:18px; }
36
+ .dissertation-paper .indented { text-indent:3em; text-align:justify; margin:0 0 8px 0; }
37
+ .prof { text-align:center; font-style:italic; margin-bottom:12px; }
38
+ .transition { margin-top:12px; font-style:italic; color:#4a6a9c; }
39
+ /* évite que l'élément hors-écran soit pris en compte dans l'affichage */
40
+ #pdf-hidden { position:absolute; left:-9999px; top:0; width:210mm; background:#fff; }
 
41
  </style>
42
  </head>
43
  <body>
44
  <div id="app" class="container">
45
+ <h1>Générer & télécharger le PDF (sans affichage)</h1>
 
46
 
47
+ <!-- Garde le textarea si tu veux fournir le sujet ; sinon supprime ce bloc -->
48
+ <textarea v-model="question" placeholder="Entrez votre sujet de dissertation ici..."></textarea>
 
 
 
 
 
49
 
50
+ <!-- Ce bouton déclenche la génération + téléchargement sans afficher la réponse -->
51
+ <!-- data-html2canvas-ignore empêche le bouton d'être capturé par html2canvas -->
52
+ <button @click="generateAndDownloadPDF" :disabled="isLoading" data-html2canvas-ignore="true">
53
+ [[ isLoading ? 'Génération et téléchargement en cours...' : 'Générer et télécharger le PDF' ]]
54
+ </button>
55
 
56
+ <div v-if="isLoading" class="loader" aria-hidden="true"></div>
57
+ <p v-if="errorMessage" style="color:#c0392b;margin-top:12px;">[[ errorMessage ]]</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  </div>
59
 
60
  <script>
61
  const { createApp } = Vue;
62
 
63
+ createApp({
64
  data() {
65
  return {
66
  question: '',
67
  isLoading: false,
68
+ errorMessage: null
 
69
  }
70
  },
71
  methods: {
72
+ // petit utilitaire pour échapper HTML (sécurité de base)
73
+ escapeHtml(str) {
74
+ if (!str) return '';
75
+ return String(str)
76
+ .replace(/&/g, '&amp;')
77
+ .replace(/</g, '&lt;')
78
+ .replace(/>/g, '&gt;')
79
+ .replace(/"/g, '&quot;')
80
+ .replace(/'/g, '&#39;');
81
+ },
82
+
83
+ // construit le HTML (mêmes classes que .dissertation-paper)
84
+ buildDissertationHTML(data) {
85
+ const e = this.escapeHtml.bind(this);
86
+ let html = '';
87
+ html += `<h2>Sujet : ${e(data.sujet || '')}</h2>`;
88
+ html += `<p class="prof">Prof : ${e(data.prof || '')}</p>`;
89
+ html += `<h3>Introduction</h3>`;
90
+ html += `<p class="indented">${e(data.introduction || '')}</p>`;
91
+
92
+ (data.parties || []).forEach(partie => {
93
+ html += `<div class="development-block">`;
94
+ html += `<p class="indented">${e(partie.chapeau || '')}</p>`;
95
+ (partie.arguments || []).forEach(arg => {
96
+ html += `<p class="indented">${e(arg.paragraphe_argumentatif || '')}</p>`;
97
+ });
98
+ html += `</div>`;
99
+ if (partie.transition) {
100
+ html += `<p class="indented transition">${e(partie.transition)}</p>`;
101
+ }
102
+ });
103
+
104
+ html += `<h3>Conclusion</h3>`;
105
+ html += `<p class="indented">${e(data.conclusion || '')}</p>`;
106
+ return html;
107
+ },
108
+
109
  async generateAndDownloadPDF() {
110
  if (!this.question.trim()) {
111
  this.errorMessage = "Veuillez entrer un sujet de dissertation.";
 
113
  }
114
  this.isLoading = true;
115
  this.errorMessage = null;
 
116
 
117
  try {
118
+ // 1) Appel API pour récupérer la dissertation (même structure JSON qu'avant)
119
+ const res = await fetch('/api/generate_dissertation', {
120
  method: 'POST',
121
  headers: { 'Content-Type': 'application/json' },
122
  body: JSON.stringify({ question: this.question })
123
  });
124
+ const data = await res.json();
125
+ if (!res.ok) throw new Error(data.error || 'Erreur serveur lors de la génération.');
 
 
 
 
 
 
 
 
 
126
 
127
+ // 2) construire le HTML à insérer dans l'élément hors-écran
128
+ const inner = this.buildDissertationHTML(data);
 
 
 
 
 
 
129
 
130
+ // 3) créer l'élément hors-écran (important : pas display:none)
131
+ let hidden = document.getElementById('pdf-hidden');
132
+ if (hidden) hidden.remove(); // nettoyage si présent
133
+ hidden = document.createElement('div');
134
+ hidden.id = 'pdf-hidden';
135
+ hidden.className = 'dissertation-paper';
136
+ hidden.style.position = 'absolute';
137
+ hidden.style.left = '-9999px';
138
+ hidden.style.top = '0';
139
+ hidden.style.width = '210mm'; // correspond à la largeur A4
140
+ hidden.style.backgroundColor = '#ffffff';
141
+ hidden.innerHTML = inner;
142
+ document.body.appendChild(hidden);
143
 
144
+ // 4) attendre le chargement des polices web (important)
145
+ if (document.fonts && document.fonts.ready) {
146
+ await document.fonts.ready;
147
+ }
148
 
149
+ // 5) config html2pdf et génération
150
  const options = {
151
+ margin: [10, 10, 10, 10], // mm
152
  filename: 'dissertation-philosophie.pdf',
153
  image: { type: 'jpeg', quality: 0.98 },
154
+ html2canvas: { scale: 2, useCORS: true, backgroundColor: '#ffffff' },
155
  jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
156
  };
157
 
158
+ await html2pdf().set(options).from(hidden).save();
159
+
160
+ // 6) nettoyage
161
+ hidden.remove();
162
+
163
  } catch (err) {
164
+ console.error('Erreur génération PDF:', err);
165
+ this.errorMessage = err.message || 'Erreur inconnue lors de la génération du PDF.';
166
+ } finally {
167
+ this.isLoading = false;
168
  }
169
  }
170
  }
171
+ })
172
+ // éviter conflit Jinja2
173
+ .config && (Vue.createApp ? null : null); // noop to keep linter tranquille
174
+
175
+ const app = createApp ? createApp() : null; // sécurité
176
+ // actually create and mount app with custom delimiters
177
+ const mountedApp = (function() {
178
+ const a = Vue.createApp ? Vue.createApp({
179
+ data: () => ({ /* unused - actual instance created above via createApp in chain */ })
180
+ }) : null;
181
+ // Instead we will create using the previously-defined createApp call:
182
+ // To avoid confusion, re-create properly:
183
+ const realApp = Vue.createApp({
184
+ data() {
185
+ return {
186
+ question: '',
187
+ isLoading: false,
188
+ errorMessage: null
189
+ };
190
+ }
191
+ });
192
+ // set delimiters to avoid Jinja conflicts
193
+ realApp.config.compilerOptions = realApp.config.compilerOptions || {};
194
+ realApp.config.compilerOptions.delimiters = ['[[', ']]'];
195
+
196
+ // mount the app and replace the placeholder methods with those defined earlier
197
+ // We'll reuse the methods from the earlier createApp block by redefining them here:
198
+ realApp.mixin({
199
+ methods: {
200
+ escapeHtml(str) {
201
+ if (!str) return '';
202
+ return String(str)
203
+ .replace(/&/g, '&amp;')
204
+ .replace(/</g, '&lt;')
205
+ .replace(/>/g, '&gt;')
206
+ .replace(/"/g, '&quot;')
207
+ .replace(/'/g, '&#39;');
208
+ },
209
+ buildDissertationHTML(data) {
210
+ const e = this.escapeHtml.bind(this);
211
+ let html = '';
212
+ html += `<h2>Sujet : ${e(data.sujet || '')}</h2>`;
213
+ html += `<p class="prof">Prof : ${e(data.prof || '')}</p>`;
214
+ html += `<h3>Introduction</h3>`;
215
+ html += `<p class="indented">${e(data.introduction || '')}</p>`;
216
+ (data.parties || []).forEach(partie => {
217
+ html += `<div class="development-block">`;
218
+ html += `<p class="indented">${e(partie.chapeau || '')}</p>`;
219
+ (partie.arguments || []).forEach(arg => {
220
+ html += `<p class="indented">${e(arg.paragraphe_argumentatif || '')}</p>`;
221
+ });
222
+ html += `</div>`;
223
+ if (partie.transition) html += `<p class="indented transition">${e(partie.transition)}</p>`;
224
+ });
225
+ html += `<h3>Conclusion</h3>`;
226
+ html += `<p class="indented">${e(data.conclusion || '')}</p>`;
227
+ return html;
228
+ },
229
+ async generateAndDownloadPDF() {
230
+ if (!this.question.trim()) {
231
+ this.errorMessage = "Veuillez entrer un sujet de dissertation.";
232
+ return;
233
+ }
234
+ this.isLoading = true;
235
+ this.errorMessage = null;
236
+ try {
237
+ const res = await fetch('/api/generate_dissertation', {
238
+ method: 'POST',
239
+ headers: { 'Content-Type': 'application/json' },
240
+ body: JSON.stringify({ question: this.question })
241
+ });
242
+ const data = await res.json();
243
+ if (!res.ok) throw new Error(data.error || 'Erreur serveur.');
244
+
245
+ const inner = this.buildDissertationHTML(data);
246
+ let hidden = document.getElementById('pdf-hidden');
247
+ if (hidden) hidden.remove();
248
+ hidden = document.createElement('div');
249
+ hidden.id = 'pdf-hidden';
250
+ hidden.className = 'dissertation-paper';
251
+ hidden.style.position = 'absolute';
252
+ hidden.style.left = '-9999px';
253
+ hidden.style.top = '0';
254
+ hidden.style.width = '210mm';
255
+ hidden.style.backgroundColor = '#ffffff';
256
+ hidden.innerHTML = inner;
257
+ document.body.appendChild(hidden);
258
+
259
+ if (document.fonts && document.fonts.ready) await document.fonts.ready;
260
+
261
+ const options = {
262
+ margin: [10,10,10,10],
263
+ filename: 'dissertation-philosophie.pdf',
264
+ image: { type: 'jpeg', quality: 0.98 },
265
+ html2canvas: { scale: 2, useCORS: true, backgroundColor: '#ffffff' },
266
+ jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
267
+ };
268
+ await html2pdf().set(options).from(hidden).save();
269
+ hidden.remove();
270
+ } catch (err) {
271
+ console.error(err);
272
+ this.errorMessage = err.message || 'Erreur lors de la génération du PDF.';
273
+ } finally {
274
+ this.isLoading = false;
275
+ }
276
+ }
277
+ }
278
+ });
279
 
280
+ return realApp.mount('#app');
281
+ })();
282
  </script>
283
  </body>
284
  </html>