free-coding-models 0.5.9 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,8 +48,8 @@
48
48
  <link rel="preconnect" href="https://fonts.googleapis.com">
49
49
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
50
50
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
51
- <script type="module" crossorigin src="/assets/index-27IYGKp2.js"></script>
52
- <link rel="stylesheet" crossorigin href="/assets/index-DKmmiDip.css">
51
+ <script type="module" crossorigin src="/assets/index-BwLMt0bw.js"></script>
52
+ <link rel="stylesheet" crossorigin href="/assets/index-BrJgRhTA.css">
53
53
  </head>
54
54
  <body>
55
55
  <div id="root"></div>
@@ -88,7 +88,10 @@ export default function InstallEndpointsView({ onClose, onToast }) {
88
88
  }
89
89
 
90
90
  const handleInstall = async () => {
91
+ // 📖 Avance au step 3 d'abord (animation "installing"), puis lance l'install
92
+ setStep(3)
91
93
  setInstalling(true)
94
+ setInstallResult(null)
92
95
  try {
93
96
  const resp = await fetch('/api/install-endpoints/wizard', {
94
97
  method: 'POST',
@@ -105,10 +108,15 @@ export default function InstallEndpointsView({ onClose, onToast }) {
105
108
  setInstallResult(data)
106
109
  onToast?.(`Installed ${data.modelCount} models for ${selectedProvider.label} into ${TOOLS.find(t => t.id === selectedTool)?.label || selectedTool}.`, 'success')
107
110
  } else {
111
+ setInstallResult(null)
108
112
  onToast?.(`Install failed: ${data.error || 'unknown'}`, 'error')
113
+ // 📖 Retour au step 2 si l'install échoue pour laisser réessayer
114
+ setStep(2)
109
115
  }
110
116
  } catch (err) {
117
+ setInstallResult(null)
111
118
  onToast?.(`Install error: ${err.message}`, 'error')
119
+ setStep(2)
112
120
  } finally {
113
121
  setInstalling(false)
114
122
  }
@@ -224,23 +232,35 @@ export default function InstallEndpointsView({ onClose, onToast }) {
224
232
  </div>
225
233
  )}
226
234
 
227
- {/* Step 3: Installing / Done */}
235
+ {/* Step 3: Installing Done */}
228
236
  {step === 3 && (
229
237
  <div className={styles.stepContent}>
230
238
  {installing && (
231
239
  <div className={styles.installingState}>
232
- <IconLoader size={24} className={styles.spinner} />
233
- <span>Installing {scope === 'all' ? 'all' : selectedModels.length} models…</span>
240
+ <IconLoader size={32} className={styles.spinner} />
241
+ <span className={styles.installingLabel}>Installing {scope === 'all' ? 'all' : selectedModels.length} model{scope === 'all' && catalogModels.length !== 1 ? 's' : selectedModels.length !== 1 ? 's' : ''}…</span>
242
+ <span className={styles.installingSub}>Writing config for {TOOLS.find(t => t.id === selectedTool)?.label}</span>
234
243
  </div>
235
244
  )}
236
- {installResult && (
245
+ {!installing && installResult && (
237
246
  <div className={styles.doneState}>
238
- <IconCheck size={32} color="#00c864" />
239
- <h3>Installation Complete</h3>
240
- <p>{installResult.modelCount} models installed into {installResult.toolLabel}</p>
247
+ <div className={styles.checkCircle}>
248
+ <IconCheck size={36} />
249
+ </div>
250
+ <h3 className={styles.doneTitle}>Installed!</h3>
251
+ <p className={styles.doneSub}>
252
+ {installResult.modelCount} model{installResult.modelCount !== 1 ? 's' : ''} from <strong>{selectedProvider?.label}</strong> → <strong>{installResult.toolLabel}</strong>
253
+ </p>
241
254
  <code className={styles.installPath}>{installResult.path}</code>
242
255
  </div>
243
256
  )}
257
+ {!installing && !installResult && (
258
+ <div className={styles.doneState}>
259
+ <span className={styles.errorDot}>✕</span>
260
+ <h3 className={styles.doneTitle}>Install failed</h3>
261
+ <p className={styles.doneSub}>Check the toast for details. You can go back and retry.</p>
262
+ </div>
263
+ )}
244
264
  </div>
245
265
  )}
246
266
  </div>
@@ -253,6 +273,13 @@ export default function InstallEndpointsView({ onClose, onToast }) {
253
273
  Back
254
274
  </button>
255
275
  )}
276
+ {/* Step 3 error: show Back to retry */}
277
+ {step === 3 && !installing && !installResult && (
278
+ <button className={styles.backBtn} onClick={() => setStep(2)}>
279
+ <IconChevronLeft size={14} />
280
+ Back
281
+ </button>
282
+ )}
256
283
  {step < 3 && (
257
284
  <button className={styles.nextBtn} onClick={handleNext} disabled={!canNext()}>
258
285
  {step === 2 ? (
@@ -268,8 +295,11 @@ export default function InstallEndpointsView({ onClose, onToast }) {
268
295
  )}
269
296
  </button>
270
297
  )}
271
- {step === 3 && installResult && (
272
- <button className={styles.doneBtn} onClick={onClose}>Done</button>
298
+ {step === 3 && !installing && installResult && (
299
+ <button className={styles.doneBtn} onClick={onClose}>
300
+ <IconCheck size={14} />
301
+ Done
302
+ </button>
273
303
  )}
274
304
  </div>
275
305
  </div>
@@ -258,23 +258,77 @@
258
258
  align-items: center;
259
259
  justify-content: center;
260
260
  gap: 12px;
261
- padding: 40px 0;
261
+ padding: 50px 0;
262
262
  color: var(--text-secondary, #aaa);
263
263
  }
264
264
 
265
+ .installingLabel {
266
+ font-size: 16px;
267
+ font-weight: 600;
268
+ color: var(--text-primary, #e0e0e0);
269
+ }
270
+
271
+ .installingSub {
272
+ font-size: 12px;
273
+ color: var(--text-muted, #888);
274
+ }
275
+
265
276
  .spinner {
266
- animation: spin 1s linear infinite;
277
+ animation: spin 0.8s linear infinite;
267
278
  }
268
279
 
269
280
  .doneState {
270
281
  display: flex;
271
282
  flex-direction: column;
272
283
  align-items: center;
273
- gap: 8px;
274
- padding: 30px 0;
284
+ gap: 10px;
285
+ padding: 40px 0;
275
286
  text-align: center;
276
- h3 { margin: 0; color: #00c864; font-size: 18px; }
277
- p { margin: 0; color: var(--text-secondary, #aaa); font-size: 13px; }
287
+ animation: doneFadeIn 0.3s ease;
288
+ }
289
+
290
+ .doneTitle {
291
+ margin: 0;
292
+ font-size: 20px;
293
+ font-weight: 700;
294
+ }
295
+
296
+ .doneSub {
297
+ margin: 0;
298
+ color: var(--text-secondary, #aaa);
299
+ font-size: 14px;
300
+ line-height: 1.5;
301
+ }
302
+
303
+ .doneSub strong {
304
+ color: var(--text-primary, #e0e0e0);
305
+ }
306
+
307
+ /* 📖 Animated check circle — scales up with a satisfying bounce */
308
+ .checkCircle {
309
+ display: flex;
310
+ align-items: center;
311
+ justify-content: center;
312
+ width: 64px;
313
+ height: 64px;
314
+ border-radius: 50%;
315
+ background: rgba(0, 200, 100, 0.12);
316
+ color: #00c864;
317
+ animation: checkPop 0.5s cubic-bezier(0.16, 1, 0.3, 1);
318
+ }
319
+
320
+ .errorDot {
321
+ display: flex;
322
+ align-items: center;
323
+ justify-content: center;
324
+ width: 64px;
325
+ height: 64px;
326
+ border-radius: 50%;
327
+ background: rgba(220, 53, 69, 0.12);
328
+ color: #dc3545;
329
+ font-size: 28px;
330
+ font-weight: 700;
331
+ animation: checkPop 0.5s cubic-bezier(0.16, 1, 0.3, 1);
278
332
  }
279
333
 
280
334
  .installPath {
@@ -288,6 +342,23 @@
288
342
  text-overflow: ellipsis;
289
343
  }
290
344
 
345
+ @keyframes doneFadeIn {
346
+ from { opacity: 0; transform: translateY(8px); }
347
+ to { opacity: 1; transform: translateY(0); }
348
+ }
349
+
350
+ @keyframes donePop {
351
+ 0% { transform: scale(0.5); opacity: 0; }
352
+ 60% { transform: scale(1.1); opacity: 1; }
353
+ 100% { transform: scale(1); opacity: 1; }
354
+ }
355
+
356
+ @keyframes checkPop {
357
+ 0% { transform: scale(0); opacity: 0; }
358
+ 50% { transform: scale(1.2); }
359
+ 100% { transform: scale(1); opacity: 1; }
360
+ }
361
+
291
362
  /* Footer */
292
363
  .footer {
293
364
  display: flex;
@@ -328,16 +399,21 @@
328
399
  }
329
400
 
330
401
  .doneBtn {
331
- padding: 8px 24px;
402
+ display: flex;
403
+ align-items: center;
404
+ gap: 6px;
405
+ padding: 10px 28px;
332
406
  background: #00c864;
333
407
  color: #000;
334
408
  border: none;
335
409
  border-radius: 8px;
336
- font-size: 13px;
337
- font-weight: 600;
410
+ font-size: 14px;
411
+ font-weight: 700;
338
412
  cursor: pointer;
339
413
  margin-left: auto;
340
- &:hover { background: #00d874; }
414
+ transition: transform 0.15s ease, background 0.15s ease;
415
+ &:hover { background: #00d874; transform: scale(1.02); }
416
+ &:active { transform: scale(0.98); }
341
417
  }
342
418
 
343
419
  @keyframes fadeIn {