Simonc-44 commited on
Commit
4ac493b
·
verified ·
1 Parent(s): 077339d

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +302 -0
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>Cygnis Nano AI</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f0f0f;
10
+ --sidebar-bg: #000000;
11
+ --card: #1a1a1a;
12
+ --accent: #ffffff;
13
+ --text: #ececec;
14
+ --text-dim: #888;
15
+ --border: #232323;
16
+ --error: #ff3333;
17
+ }
18
+
19
+ * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Inter', system-ui, sans-serif; }
20
+
21
+ body { background-color: var(--bg); color: var(--text); height: 100vh; display: flex; overflow: hidden; }
22
+
23
+ /* Barre Latérale */
24
+ .sidebar {
25
+ width: 260px;
26
+ background-color: var(--sidebar-bg);
27
+ border-right: 1px solid var(--border);
28
+ display: flex;
29
+ flex-direction: column;
30
+ padding: 20px;
31
+ flex-shrink: 0;
32
+ }
33
+
34
+ .new-chat-btn {
35
+ border: 1px solid var(--border);
36
+ background: transparent;
37
+ color: var(--text);
38
+ padding: 12px;
39
+ border-radius: 8px;
40
+ cursor: pointer;
41
+ font-size: 0.9rem;
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 10px;
45
+ transition: background 0.2s;
46
+ }
47
+
48
+ .new-chat-btn:hover { background: #161616; }
49
+
50
+ /* Zone de contenu principale */
51
+ .app-container {
52
+ flex: 1;
53
+ display: flex;
54
+ flex-direction: column;
55
+ height: 100%;
56
+ position: relative;
57
+ }
58
+
59
+ header {
60
+ padding: 15px 25px;
61
+ border-bottom: 1px solid var(--border);
62
+ display: flex;
63
+ justify-content: space-between;
64
+ align-items: center;
65
+ }
66
+
67
+ .logo { font-weight: 700; font-size: 1.1rem; letter-spacing: -0.5px; }
68
+
69
+ main {
70
+ flex: 1;
71
+ overflow-y: auto;
72
+ padding: 25px 15%; /* Espacement pour centrer le contenu sur grand écran */
73
+ scroll-behavior: smooth;
74
+ display: flex;
75
+ flex-direction: column;
76
+ }
77
+
78
+ .welcome-page {
79
+ margin: auto;
80
+ text-align: center;
81
+ max-width: 600px;
82
+ animation: fadeIn 0.8s ease;
83
+ }
84
+
85
+ .welcome-page h1 {
86
+ font-size: 2.2rem;
87
+ margin-bottom: 15px;
88
+ font-weight: 800;
89
+ letter-spacing: -1px;
90
+ }
91
+
92
+ .msg-container { margin-bottom: 30px; width: 100%; display: flex; flex-direction: column; }
93
+ .user { align-self: flex-end; background: var(--card); padding: 12px 20px; border-radius: 18px 18px 2px 18px; border: 1px solid var(--border); max-width: 80%; }
94
+ .ai { align-self: flex-start; width: 100%; }
95
+
96
+ .role-label { font-size: 0.75rem; font-weight: 800; color: var(--text-dim); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px; }
97
+ .msg-content { white-space: pre-wrap; line-height: 1.7; font-size: 1.05rem; }
98
+
99
+ footer {
100
+ padding: 20px 15%;
101
+ background: var(--bg);
102
+ display: flex;
103
+ flex-direction: column;
104
+ align-items: center;
105
+ }
106
+
107
+ .input-wrapper {
108
+ width: 100%;
109
+ max-width: 800px;
110
+ background: var(--card);
111
+ border: 1px solid var(--border);
112
+ border-radius: 16px;
113
+ display: flex;
114
+ align-items: center;
115
+ padding: 10px 15px;
116
+ min-height: 56px;
117
+ }
118
+
119
+ textarea {
120
+ flex: 1;
121
+ background: transparent;
122
+ border: none;
123
+ color: var(--text);
124
+ resize: none;
125
+ padding: 10px 0;
126
+ font-size: 1rem;
127
+ outline: none;
128
+ max-height: 200px;
129
+ line-height: 1.5;
130
+ }
131
+
132
+ #actionBtn {
133
+ background: var(--accent);
134
+ color: #000;
135
+ border: none;
136
+ width: 36px;
137
+ height: 36px;
138
+ border-radius: 10px;
139
+ cursor: pointer;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ transition: all 0.2s;
144
+ flex-shrink: 0;
145
+ margin-left: 10px;
146
+ }
147
+
148
+ #actionBtn.stop-mode { background: var(--error); color: white; }
149
+
150
+ .disclaimer { font-size: 0.75rem; color: var(--text-dim); margin-top: 15px; }
151
+
152
+ .error-card { background: rgba(255, 51, 51, 0.1); border: 1px solid var(--error); padding: 20px; border-radius: 12px; margin-top: 15px; text-align: center; width: 100%; }
153
+ .error-card a { color: var(--error); font-weight: 700; text-decoration: none; border-bottom: 1px solid var(--error); }
154
+
155
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
156
+ .streaming::after { content: '▊'; margin-left: 2px; animation: blink 0.8s infinite; }
157
+ @keyframes blink { 50% { opacity: 0; } }
158
+ </style>
159
+ </head>
160
+ <body>
161
+
162
+ <aside class="sidebar">
163
+ <button class="new-chat-btn" onclick="location.reload()">
164
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
165
+ Nouveau Chat
166
+ </button>
167
+ <div style="margin-top: auto; color: var(--text-dim); font-size: 0.8rem; text-align: center;">
168
+ Cygnis Nano v1.2
169
+ </div>
170
+ </aside>
171
+
172
+ <div class="app-container">
173
+ <header>
174
+ <div class="logo">Cygnis Nano</div>
175
+ </header>
176
+
177
+ <main id="chatMessages">
178
+ <div id="welcomePage" class="welcome-page">
179
+ <h1>Bonjour, comment puis-je vous aider aujourd'hui ?</h1>
180
+ <p>Posez-moi une question pour commencer une conversation avec Cygnis Nano.</p>
181
+ </div>
182
+ </main>
183
+
184
+ <footer>
185
+ <div class="input-wrapper">
186
+ <textarea id="chatInput" placeholder="Envoyez un message..." rows="1"></textarea>
187
+ <button id="actionBtn">
188
+ <span id="btnIcon">
189
+ <svg width="22" height="22" viewBox="0 0 20 20" fill="currentColor"><path d="M8.99992 16V6.41407L5.70696 9.70704C5.31643 10.0976 4.68342 10.0976 4.29289 9.70704C3.90237 9.31652 3.90237 8.6835 4.29289 8.29298L9.29289 3.29298L9.36907 3.22462C9.76184 2.90427 10.3408 2.92686 10.707 3.29298L15.707 8.29298L15.7753 8.36915C16.0957 8.76192 16.0731 9.34092 15.707 9.70704C15.3408 10.0732 14.7618 10.0958 14.3691 9.7754L14.2929 9.70704L10.9999 6.41407V16C10.9999 16.5523 10.5522 17 9.99992 17C9.44764 17 8.99992 16.5523 8.99992 16Z" transform="rotate(90 10 10)"></path></svg>
190
+ </span>
191
+ </button>
192
+ </div>
193
+ <p class="disclaimer">L'IA peut faire des erreurs. Envisagez de vérifier les informations importantes.</p>
194
+ </footer>
195
+ </div>
196
+
197
+ <script>
198
+ const chatMessages = document.getElementById('chatMessages');
199
+ const chatInput = document.getElementById('chatInput');
200
+ const actionBtn = document.getElementById('actionBtn');
201
+ const btnIcon = document.getElementById('btnIcon');
202
+ const welcomePage = document.getElementById('welcomePage');
203
+
204
+ const NANO_API = "https://simonc-44-cygnis-nano.hf.space/ask";
205
+ const WAKE_URL = "https://huggingface.co/spaces/Simonc-44/Cygnis-Nano";
206
+
207
+ let isGenerating = false;
208
+ let abortController = null;
209
+
210
+ chatInput.oninput = function() {
211
+ this.style.height = 'auto';
212
+ this.style.height = (this.scrollHeight) + 'px';
213
+ };
214
+
215
+ function setUIState(generating) {
216
+ isGenerating = generating;
217
+ if (generating) {
218
+ actionBtn.classList.add('stop-mode');
219
+ btnIcon.innerHTML = `<svg width="14" height="14" viewBox="0 0 14 14"><rect x="0" y="0" width="14" height="14" fill="currentColor"/></svg>`;
220
+ } else {
221
+ actionBtn.classList.remove('stop-mode');
222
+ btnIcon.innerHTML = `<svg width="22" height="22" viewBox="0 0 20 20" fill="currentColor"><path d="M8.99992 16V6.41407L5.70696 9.70704C5.31643 10.0976 4.68342 10.0976 4.29289 9.70704C3.90237 9.31652 3.90237 8.6835 4.29289 8.29298L9.29289 3.29298L9.36907 3.22462C9.76184 2.90427 10.3408 2.92686 10.707 3.29298L15.707 8.29298L15.7753 8.36915C16.0957 8.76192 16.0731 9.34092 15.707 9.70704C15.3408 10.0732 14.7618 10.0958 14.3691 9.7754L14.2929 9.70704L10.9999 6.41407V16C10.9999 16.5523 10.5522 17 9.99992 17C9.44764 17 8.99992 16.5523 8.99992 16Z" transform="rotate(90 10 10)"></path></svg>`;
223
+ }
224
+ }
225
+
226
+ async function handleAction() {
227
+ if (isGenerating) {
228
+ if (abortController) abortController.abort();
229
+ setUIState(false);
230
+ return;
231
+ }
232
+
233
+ const text = chatInput.value.trim();
234
+ if (!text) return;
235
+
236
+ if (welcomePage) welcomePage.remove();
237
+
238
+ appendMsg(text, 'user');
239
+ chatInput.value = "";
240
+ chatInput.style.height = 'auto';
241
+
242
+ const aiContent = appendMsg("", 'ai');
243
+ aiContent.classList.add('streaming');
244
+ setUIState(true);
245
+
246
+ abortController = new AbortController();
247
+
248
+ try {
249
+ const response = await fetch(NANO_API, {
250
+ method: "POST",
251
+ headers: { "Content-Type": "application/json" },
252
+ body: JSON.stringify({ prompt: text }),
253
+ signal: abortController.signal
254
+ });
255
+
256
+ if (!response.ok) throw new Error('OFFLINE');
257
+
258
+ const data = await response.json();
259
+ aiContent.innerText = data.full_response;
260
+ } catch (e) {
261
+ if (e.name === 'AbortError') {
262
+ aiContent.innerText = "Génération interrompue.";
263
+ } else {
264
+ aiContent.innerHTML = `
265
+ <div class="error-card">
266
+ <p><strong>Cygnis Nano ne répond pas.</strong></p>
267
+ <p style="font-size:0.85rem; margin:10px 0;">Le serveur distant (Hugging Face) est probablement en veille.</p>
268
+ <a href="${WAKE_URL}" target="_blank">Réveiller le modèle</a>
269
+ </div>`;
270
+ }
271
+ } finally {
272
+ aiContent.classList.remove('streaming');
273
+ setUIState(false);
274
+ chatMessages.scrollTop = chatMessages.scrollHeight;
275
+ }
276
+ }
277
+
278
+ function appendMsg(text, role) {
279
+ const div = document.createElement('div');
280
+ div.className = `msg-container ${role}`;
281
+ if (role === 'ai') {
282
+ div.innerHTML = `<div class="role-label">Cygnis Nano</div><div class="msg-content"></div>`;
283
+ chatMessages.appendChild(div);
284
+ return div.querySelector('.msg-content');
285
+ } else {
286
+ div.innerHTML = `<div class="msg-content">${text}</div>`;
287
+ chatMessages.appendChild(div);
288
+ chatMessages.scrollTop = chatMessages.scrollHeight;
289
+ return div;
290
+ }
291
+ }
292
+
293
+ actionBtn.onclick = handleAction;
294
+ chatInput.onkeydown = (e) => {
295
+ if(e.key === 'Enter' && !e.shiftKey) {
296
+ e.preventDefault();
297
+ handleAction();
298
+ }
299
+ };
300
+ </script>
301
+ </body>
302
+ </html>