@lastbrain/ai-ui-react 1.0.36 → 1.0.38

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.
Files changed (33) hide show
  1. package/dist/components/AiContextButton.d.ts.map +1 -1
  2. package/dist/components/AiContextButton.js +1 -9
  3. package/dist/components/AiImageButton.d.ts.map +1 -1
  4. package/dist/components/AiImageButton.js +1 -16
  5. package/dist/components/AiInput.d.ts.map +1 -1
  6. package/dist/components/AiInput.js +2 -2
  7. package/dist/components/AiPromptPanel.d.ts +1 -0
  8. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  9. package/dist/components/AiPromptPanel.js +278 -169
  10. package/dist/components/AiTextarea.d.ts.map +1 -1
  11. package/dist/components/AiTextarea.js +1 -1
  12. package/dist/components/ErrorToast.js +14 -14
  13. package/dist/context/AiProvider.d.ts.map +1 -1
  14. package/dist/context/AiProvider.js +87 -26
  15. package/dist/hooks/useAiClient.d.ts +1 -0
  16. package/dist/hooks/useAiClient.d.ts.map +1 -1
  17. package/dist/hooks/useAiModels.d.ts.map +1 -1
  18. package/dist/hooks/useAiModels.js +1 -2
  19. package/dist/hooks/useModelManagement.d.ts.map +1 -1
  20. package/dist/hooks/useModelManagement.js +24 -3
  21. package/dist/hooks/usePrompts.d.ts.map +1 -1
  22. package/dist/hooks/usePrompts.js +19 -3
  23. package/package.json +2 -2
  24. package/src/components/AiContextButton.tsx +2 -9
  25. package/src/components/AiImageButton.tsx +5 -17
  26. package/src/components/AiInput.tsx +3 -2
  27. package/src/components/AiPromptPanel.tsx +332 -192
  28. package/src/components/AiTextarea.tsx +2 -1
  29. package/src/components/ErrorToast.tsx +16 -16
  30. package/src/context/AiProvider.tsx +116 -31
  31. package/src/hooks/useAiModels.ts +1 -2
  32. package/src/hooks/useModelManagement.ts +33 -3
  33. package/src/hooks/usePrompts.ts +21 -3
@@ -31,6 +31,7 @@ export interface AiPromptPanelProps {
31
31
  children?: (props: AiPromptPanelRenderProps) => ReactNode;
32
32
  // Nouvelles props pour la gestion avancée des modèles
33
33
  enableModelManagement?: boolean;
34
+ modelCategory?: "text" | "image"; // Catégorie de modèles à gérer
34
35
  availableModels?: AIModel[];
35
36
  userModels?: string[];
36
37
  onModelToggle?: (modelId: string, isActive: boolean) => Promise<void>;
@@ -80,7 +81,8 @@ function AiPromptPanelInternal({
80
81
  models = [],
81
82
  sourceText,
82
83
  children,
83
- enableModelManagement = false,
84
+ enableModelManagement = true,
85
+ modelCategory = "text",
84
86
  availableModels = [],
85
87
  userModels = [],
86
88
  onModelToggle,
@@ -107,6 +109,7 @@ function AiPromptPanelInternal({
107
109
  const [showAllModels, setShowAllModels] = useState(false);
108
110
  const [isModelManagementOpen, setIsModelManagementOpen] = useState(false);
109
111
  const [loadingModels, setLoadingModels] = useState<string[]>([]);
112
+ const [modelSearchQuery, setModelSearchQuery] = useState("");
110
113
 
111
114
  const {
112
115
  prompts,
@@ -119,7 +122,7 @@ function AiPromptPanelInternal({
119
122
  const autoModelManagement = useModelManagement({
120
123
  apiKey,
121
124
  baseUrl,
122
- category: "text", // Par défaut pour AiPromptPanel
125
+ category: modelCategory, // Utiliser la catégorie spécifiée
123
126
  autoFetch:
124
127
  enableModelManagement &&
125
128
  models.length === 0 &&
@@ -173,27 +176,37 @@ function AiPromptPanelInternal({
173
176
  }));
174
177
  }
175
178
 
176
- const textModels = effectiveAvailableModels.filter(
177
- (m) => m.category === "text"
179
+ const categoryModels = effectiveAvailableModels.filter(
180
+ (m) => m.category === modelCategory
178
181
  );
179
182
 
180
183
  if (showAllModels) {
181
- return textModels;
184
+ return categoryModels;
182
185
  } else {
183
- return textModels.filter((m) => effectiveUserModels.includes(m.id));
186
+ return categoryModels.filter((m) => effectiveUserModels.includes(m.id));
184
187
  }
185
188
  };
186
189
 
187
190
  // Fetch prompts when modal opens
188
191
  useEffect(() => {
189
- if (isOpen && models.length > 0) {
190
- const activeModelId = selectedModel || models[0]?.id;
191
- const modelType = models.find((m) => m.id === activeModelId)?.type;
192
+ if (isOpen && (models.length > 0 || enableModelManagement)) {
193
+ // Déterminer le type de modèle à partir de modelCategory si enableModelManagement
194
+ const modelType = enableModelManagement
195
+ ? modelCategory
196
+ : models.find((m) => m.id === selectedModel)?.type || models[0]?.type;
197
+
192
198
  fetchPrompts({
193
199
  type: modelType === "image" ? "image" : "text",
194
200
  });
195
201
  }
196
- }, [isOpen, selectedModel, models, fetchPrompts]);
202
+ }, [
203
+ isOpen,
204
+ selectedModel,
205
+ models,
206
+ fetchPrompts,
207
+ enableModelManagement,
208
+ modelCategory,
209
+ ]);
197
210
 
198
211
  const handleSubmit = async () => {
199
212
  const activeModelId = selectedModel || models[0]?.id;
@@ -354,6 +367,10 @@ function AiPromptPanelInternal({
354
367
  opacity: isClosing ? 0 : 1,
355
368
  transform: isClosing ? "translateY(12px)" : "translateY(0)",
356
369
  transition: "opacity 200ms ease, transform 200ms ease",
370
+ display: "flex",
371
+ flexDirection: "column",
372
+ maxHeight: "85vh",
373
+ overflow: "hidden",
357
374
  }}
358
375
  >
359
376
  {isGenerating && (
@@ -401,7 +418,17 @@ function AiPromptPanelInternal({
401
418
  </div>
402
419
  </div>
403
420
  )}
404
- <div style={aiStyles.modalHeader}>
421
+ <div
422
+ style={{
423
+ ...aiStyles.modalHeader,
424
+ position: "sticky",
425
+ top: 0,
426
+ zIndex: 5,
427
+ background: "var(--ai-bg-primary, #1f2937)",
428
+ borderBottom: "1px solid var(--ai-border-primary, #374151)",
429
+ backdropFilter: "blur(8px)",
430
+ }}
431
+ >
405
432
  <h2 style={aiStyles.modalTitle}>
406
433
  {showPromptLibrary ? "Select a Prompt" : "AI Prompt Configuration"}
407
434
  </h2>
@@ -419,7 +446,13 @@ function AiPromptPanelInternal({
419
446
  </button>
420
447
  </div>
421
448
 
422
- <div style={aiStyles.modalBody}>
449
+ <div
450
+ style={{
451
+ ...aiStyles.modalBody,
452
+ flex: 1,
453
+ overflow: "auto",
454
+ }}
455
+ >
423
456
  {!showPromptLibrary ? (
424
457
  <>
425
458
  {sourceText && (
@@ -464,66 +497,11 @@ function AiPromptPanelInternal({
464
497
  <label htmlFor="model-select" style={aiStyles.modalLabel}>
465
498
  AI Model
466
499
  </label>
467
- <div
468
- style={{
469
- display: "flex",
470
- alignItems: "center",
471
- gap: "8px",
472
- }}
473
- >
474
- {effectiveAvailableModels.length > 0 && (
475
- <button
476
- onClick={() => setShowAllModels(!showAllModels)}
477
- style={{
478
- padding: "4px 8px",
479
- fontSize: "12px",
480
- color: "#8b5cf6",
481
- background: "#8b5cf610",
482
- border: "1px solid #8b5cf630",
483
- borderRadius: "6px",
484
- cursor: "pointer",
485
- transition: "all 0.2s",
486
- }}
487
- onMouseEnter={(e) => {
488
- e.currentTarget.style.background = "#8b5cf620";
489
- }}
490
- onMouseLeave={(e) => {
491
- e.currentTarget.style.background = "#8b5cf610";
492
- }}
493
- >
494
- {showAllModels ? "Mes modèles" : "Tous les modèles"}
495
- {!showAllModels &&
496
- effectiveAvailableModels.filter(
497
- (m) =>
498
- m.category === "text" &&
499
- !effectiveUserModels.includes(m.id)
500
- ).length > 0 && (
501
- <span
502
- style={{
503
- marginLeft: "4px",
504
- padding: "2px 6px",
505
- fontSize: "10px",
506
- background: "#8b5cf6",
507
- color: "white",
508
- borderRadius: "10px",
509
- }}
510
- >
511
- +
512
- {
513
- effectiveAvailableModels.filter(
514
- (m) =>
515
- m.category === "text" &&
516
- !effectiveUserModels.includes(m.id)
517
- ).length
518
- }
519
- </span>
520
- )}
521
- </button>
522
- )}
500
+ {effectiveAvailableModels.length > 0 && (
523
501
  <button
524
502
  onClick={() => setIsModelManagementOpen(true)}
525
503
  style={{
526
- padding: "4px 8px",
504
+ padding: "4px 12px",
527
505
  fontSize: "12px",
528
506
  color: "#8b5cf6",
529
507
  background: "#8b5cf610",
@@ -533,7 +511,7 @@ function AiPromptPanelInternal({
533
511
  transition: "all 0.2s",
534
512
  display: "flex",
535
513
  alignItems: "center",
536
- gap: "4px",
514
+ gap: "6px",
537
515
  }}
538
516
  onMouseEnter={(e) => {
539
517
  e.currentTarget.style.background = "#8b5cf620";
@@ -543,10 +521,36 @@ function AiPromptPanelInternal({
543
521
  }}
544
522
  title="Gérer les modèles"
545
523
  >
546
- <Settings size={12} />
547
- Gérer
524
+ <Settings size={14} />
525
+ Gérer les modèles
526
+ {effectiveAvailableModels.filter(
527
+ (m) =>
528
+ m.category === modelCategory &&
529
+ !effectiveUserModels.includes(m.id)
530
+ ).length > 0 && (
531
+ <span
532
+ style={{
533
+ marginLeft: "2px",
534
+ padding: "2px 6px",
535
+ fontSize: "10px",
536
+ background: "#8b5cf6",
537
+ color: "white",
538
+ borderRadius: "10px",
539
+ fontWeight: "600",
540
+ }}
541
+ >
542
+ +
543
+ {
544
+ effectiveAvailableModels.filter(
545
+ (m) =>
546
+ m.category === modelCategory &&
547
+ !effectiveUserModels.includes(m.id)
548
+ ).length
549
+ }
550
+ </span>
551
+ )}
548
552
  </button>
549
- </div>
553
+ )}
550
554
  </div>
551
555
  <select
552
556
  id="model-select"
@@ -993,7 +997,17 @@ function AiPromptPanelInternal({
993
997
  )}
994
998
  </div>
995
999
 
996
- <div style={aiStyles.modalFooter}>
1000
+ <div
1001
+ style={{
1002
+ ...aiStyles.modalFooter,
1003
+ position: "sticky",
1004
+ bottom: 0,
1005
+ zIndex: 5,
1006
+ background: "var(--ai-bg-primary, #1f2937)",
1007
+ borderTop: "1px solid var(--ai-border-primary, #374151)",
1008
+ backdropFilter: "blur(8px)",
1009
+ }}
1010
+ >
997
1011
  <button
998
1012
  onClick={handleClose}
999
1013
  onMouseEnter={() => setIsCancelHovered(true)}
@@ -1037,36 +1051,52 @@ function AiPromptPanelInternal({
1037
1051
  <div
1038
1052
  style={{
1039
1053
  ...aiStyles.modalOverlay,
1040
- backgroundColor: "rgba(0, 0, 0, 0.7)",
1054
+ backgroundColor: "rgba(0, 0, 0, 0.75)",
1055
+ }}
1056
+ onClick={() => {
1057
+ setIsModelManagementOpen(false);
1058
+ setModelSearchQuery("");
1041
1059
  }}
1042
- onClick={() => setIsModelManagementOpen(false)}
1043
1060
  />
1044
1061
  <div
1045
1062
  style={{
1046
1063
  ...aiStyles.modalContent,
1047
- maxWidth: "600px",
1048
- maxHeight: "80vh",
1049
- overflow: "auto",
1064
+ maxWidth: "700px",
1065
+ maxHeight: "85vh",
1066
+ overflow: "hidden",
1067
+ display: "flex",
1068
+ flexDirection: "column",
1069
+ background: "var(--ai-bg-primary, #1f2937)",
1070
+ boxShadow: "0 20px 40px rgba(0, 0, 0, 0.5)",
1050
1071
  }}
1051
1072
  >
1052
1073
  <div style={aiStyles.modalHeader}>
1053
1074
  <h2 style={aiStyles.modalTitle}>Gestion des modèles IA</h2>
1054
1075
  <button
1055
1076
  style={aiStyles.modalCloseButton}
1056
- onClick={() => setIsModelManagementOpen(false)}
1077
+ onClick={() => {
1078
+ setIsModelManagementOpen(false);
1079
+ setModelSearchQuery("");
1080
+ }}
1057
1081
  aria-label="Close"
1058
1082
  >
1059
1083
  ×
1060
1084
  </button>
1061
1085
  </div>
1062
1086
 
1063
- <div style={aiStyles.modalBody}>
1087
+ <div
1088
+ style={{
1089
+ ...aiStyles.modalBody,
1090
+ flex: 1,
1091
+ overflow: "auto",
1092
+ }}
1093
+ >
1064
1094
  <div style={{ marginBottom: "16px" }}>
1065
1095
  <p
1066
1096
  style={{
1067
1097
  fontSize: "14px",
1068
- color: "#6b7280",
1069
- margin: "0 0 16px 0",
1098
+ color: "var(--ai-text-secondary, #6b7280)",
1099
+ margin: "0 0 8px 0",
1070
1100
  }}
1071
1101
  >
1072
1102
  Activez ou désactivez les modèles selon vos besoins
@@ -1074,36 +1104,73 @@ function AiPromptPanelInternal({
1074
1104
  <p
1075
1105
  style={{
1076
1106
  fontSize: "12px",
1077
- color: "#9ca3af",
1078
- margin: "0",
1107
+ color: "var(--ai-text-tertiary, #9ca3af)",
1108
+ margin: "0 0 16px 0",
1079
1109
  }}
1080
1110
  >
1081
1111
  {
1082
1112
  effectiveAvailableModels.filter(
1083
- (m) => m.category === "text"
1113
+ (m) => m.category === modelCategory
1084
1114
  ).length
1085
1115
  }{" "}
1086
1116
  modèles disponibles •{" "}
1087
1117
  {
1088
1118
  effectiveUserModels.filter((id) =>
1089
1119
  effectiveAvailableModels.some(
1090
- (m) => m.id === id && m.category === "text"
1120
+ (m) => m.id === id && m.category === modelCategory
1091
1121
  )
1092
1122
  ).length
1093
1123
  }{" "}
1094
1124
  activés
1095
1125
  </p>
1126
+ {/* Champ de recherche */}
1127
+ <div
1128
+ style={{
1129
+ ...aiStyles.inputWrapper,
1130
+ marginBottom: "16px",
1131
+ }}
1132
+ >
1133
+ <Search
1134
+ size={16}
1135
+ style={{
1136
+ position: "absolute",
1137
+ left: "12px",
1138
+ top: "50%",
1139
+ transform: "translateY(-50%)",
1140
+ color: "var(--ai-text-tertiary, #9ca3af)",
1141
+ }}
1142
+ />
1143
+ <input
1144
+ value={modelSearchQuery}
1145
+ onChange={(e) => setModelSearchQuery(e.target.value)}
1146
+ placeholder="Rechercher un modèle..."
1147
+ style={{
1148
+ ...aiStyles.input,
1149
+ padding: "10px 12px 10px 36px",
1150
+ background: "var(--ai-bg-secondary, #f9fafb)",
1151
+ }}
1152
+ />
1153
+ </div>
1096
1154
  </div>
1097
1155
 
1098
1156
  <div
1099
1157
  style={{
1100
1158
  display: "flex",
1101
1159
  flexDirection: "column",
1102
- gap: "12px",
1160
+ gap: "8px",
1103
1161
  }}
1104
1162
  >
1105
1163
  {effectiveAvailableModels
1106
- .filter((model) => model.category === "text")
1164
+ .filter((model) => {
1165
+ if (model.category !== modelCategory) return false;
1166
+ if (!modelSearchQuery.trim()) return true;
1167
+ const query = modelSearchQuery.toLowerCase();
1168
+ return (
1169
+ model.name.toLowerCase().includes(query) ||
1170
+ model.provider.toLowerCase().includes(query) ||
1171
+ model.description?.toLowerCase().includes(query)
1172
+ );
1173
+ })
1107
1174
  .map((modelData) => {
1108
1175
  const isActive = effectiveUserModels.includes(modelData.id);
1109
1176
  const isLoading = loadingModels.includes(modelData.id);
@@ -1115,41 +1182,78 @@ function AiPromptPanelInternal({
1115
1182
  display: "flex",
1116
1183
  alignItems: "center",
1117
1184
  justifyContent: "space-between",
1118
- padding: "16px",
1119
- border: "1px solid #e5e7eb",
1120
- borderRadius: "8px",
1121
- backgroundColor: isActive ? "#f0fdf4" : "#ffffff",
1185
+ padding: "16px 18px",
1186
+ border: "2px solid",
1187
+ borderColor: isActive
1188
+ ? "#10b981"
1189
+ : "var(--ai-border-primary, #374151)",
1190
+ borderRadius: "10px",
1191
+ backgroundColor: isActive
1192
+ ? "rgba(16, 185, 129, 0.08)"
1193
+ : "var(--ai-bg-secondary, rgba(31, 41, 55, 0.5))",
1122
1194
  transition: "all 0.2s",
1195
+ cursor: "pointer",
1196
+ backdropFilter: "blur(8px)",
1197
+ }}
1198
+ onClick={() =>
1199
+ !isLoading &&
1200
+ handleModelToggle(modelData.id, !isActive)
1201
+ }
1202
+ onMouseEnter={(e) => {
1203
+ e.currentTarget.style.borderColor = isActive
1204
+ ? "#059669"
1205
+ : "#4b5563";
1206
+ e.currentTarget.style.transform = "translateY(-2px)";
1207
+ e.currentTarget.style.boxShadow = isActive
1208
+ ? "0 8px 16px rgba(16, 185, 129, 0.2)"
1209
+ : "0 8px 16px rgba(0, 0, 0, 0.15)";
1210
+ }}
1211
+ onMouseLeave={(e) => {
1212
+ e.currentTarget.style.borderColor = isActive
1213
+ ? "#10b981"
1214
+ : "var(--ai-border-primary, #374151)";
1215
+ e.currentTarget.style.transform = "translateY(0)";
1216
+ e.currentTarget.style.boxShadow = "none";
1123
1217
  }}
1124
1218
  >
1125
1219
  <div
1126
1220
  style={{
1127
1221
  display: "flex",
1128
1222
  alignItems: "center",
1129
- gap: "12px",
1223
+ gap: "14px",
1224
+ flex: 1,
1130
1225
  }}
1131
1226
  >
1227
+ {/* Status indicator */}
1132
1228
  <div
1133
1229
  style={{
1134
- width: "12px",
1135
- height: "12px",
1230
+ width: "8px",
1231
+ height: "8px",
1136
1232
  borderRadius: "50%",
1137
- backgroundColor: isActive ? "#10b981" : "#d1d5db",
1233
+ backgroundColor: isActive ? "#10b981" : "#6b7280",
1234
+ boxShadow: isActive
1235
+ ? "0 0 8px rgba(16, 185, 129, 0.6)"
1236
+ : "none",
1237
+ transition: "all 0.3s",
1138
1238
  }}
1139
1239
  />
1140
- <div>
1240
+ <div style={{ flex: 1 }}>
1141
1241
  <div
1142
1242
  style={{
1143
1243
  display: "flex",
1144
1244
  alignItems: "center",
1145
- gap: "8px",
1146
- marginBottom: "4px",
1245
+ gap: "10px",
1246
+ marginBottom: "6px",
1147
1247
  }}
1148
1248
  >
1149
1249
  <span
1150
1250
  style={{
1151
- fontWeight: "500",
1152
- color: "#111827",
1251
+ fontWeight: "600",
1252
+ fontSize: "15px",
1253
+ color: isActive
1254
+ ? "#10b981"
1255
+ : "var(--ai-text-primary, #f3f4f6)",
1256
+ letterSpacing: "-0.01em",
1153
1257
  }}
1154
1258
  >
1155
1259
  {modelData.name}
@@ -1157,132 +1261,168 @@ function AiPromptPanelInternal({
1157
1261
  {modelData.isPro && (
1158
1262
  <span
1159
1263
  style={{
1160
- padding: "2px 8px",
1161
- fontSize: "11px",
1264
+ padding: "3px 10px",
1265
+ fontSize: "10px",
1162
1266
  backgroundColor: "#8b5cf6",
1163
1267
  color: "white",
1164
1268
  borderRadius: "12px",
1165
- fontWeight: "500",
1269
+ fontWeight: "700",
1270
+ textTransform: "uppercase",
1271
+ letterSpacing: "0.05em",
1272
+ boxShadow:
1273
+ "0 2px 8px rgba(139, 92, 246, 0.3)",
1166
1274
  }}
1167
1275
  >
1168
1276
  PRO
1169
1277
  </span>
1170
1278
  )}
1171
1279
  </div>
1172
- {modelData.description && (
1173
- <p
1174
- style={{
1175
- fontSize: "13px",
1176
- color: "#6b7280",
1177
- margin: "0 0 4px 0",
1178
- }}
1179
- >
1180
- {modelData.description}
1181
- </p>
1182
- )}
1183
1280
  <div
1184
1281
  style={{
1185
1282
  display: "flex",
1186
1283
  alignItems: "center",
1187
1284
  gap: "16px",
1188
- fontSize: "12px",
1189
- color: "#9ca3af",
1285
+ fontSize: "13px",
1286
+ color: isActive
1287
+ ? "#6ee7b7"
1288
+ : "var(--ai-text-secondary, #d1d5db)",
1289
+ fontWeight: "500",
1190
1290
  }}
1191
1291
  >
1192
- <span>Fournisseur: {modelData.provider}</span>
1292
+ <span
1293
+ style={{
1294
+ display: "flex",
1295
+ alignItems: "center",
1296
+ gap: "6px",
1297
+ }}
1298
+ >
1299
+ <span
1300
+ style={{
1301
+ display: "inline-block",
1302
+ width: "4px",
1303
+ height: "4px",
1304
+ borderRadius: "50%",
1305
+ backgroundColor: "currentColor",
1306
+ }}
1307
+ />
1308
+ {modelData.provider}
1309
+ </span>
1193
1310
  {modelData.costPer1M && (
1194
- <span>
1195
- Coût: ${modelData.costPer1M}/1M tokens
1311
+ <span
1312
+ style={{
1313
+ padding: "2px 8px",
1314
+ background: isActive
1315
+ ? "rgba(16, 185, 129, 0.15)"
1316
+ : "rgba(107, 114, 128, 0.2)",
1317
+ borderRadius: "6px",
1318
+ fontSize: "12px",
1319
+ }}
1320
+ >
1321
+ ${modelData.costPer1M}/1M
1196
1322
  </span>
1197
1323
  )}
1198
1324
  </div>
1199
1325
  </div>
1200
1326
  </div>
1201
- <button
1202
- onClick={() =>
1203
- handleModelToggle(modelData.id, !isActive)
1204
- }
1205
- disabled={isLoading}
1327
+ {/* Toggle Switch CSS amélioré */}
1328
+ <label
1206
1329
  style={{
1207
- padding: "8px 16px",
1208
- fontSize: "13px",
1209
- fontWeight: "500",
1210
- borderRadius: "6px",
1211
- border: "1px solid",
1330
+ position: "relative",
1331
+ display: "inline-block",
1332
+ width: "54px",
1333
+ height: "28px",
1212
1334
  cursor: isLoading ? "not-allowed" : "pointer",
1213
- transition: "all 0.2s",
1214
- minWidth: "80px",
1215
- ...(isActive
1216
- ? {
1217
- backgroundColor: "#fef2f2",
1218
- borderColor: "#fecaca",
1219
- color: "#dc2626",
1220
- }
1221
- : {
1222
- backgroundColor: "#f0fdf4",
1223
- borderColor: "#bbf7d0",
1224
- color: "#16a34a",
1225
- }),
1226
- opacity: isLoading ? 0.6 : 1,
1227
- }}
1228
- onMouseEnter={(e) => {
1229
- if (!isLoading) {
1230
- if (isActive) {
1231
- e.currentTarget.style.backgroundColor =
1232
- "#fee2e2";
1233
- } else {
1234
- e.currentTarget.style.backgroundColor =
1235
- "#dcfce7";
1236
- }
1237
- }
1238
- }}
1239
- onMouseLeave={(e) => {
1240
- if (!isLoading) {
1241
- if (isActive) {
1242
- e.currentTarget.style.backgroundColor =
1243
- "#fef2f2";
1244
- } else {
1245
- e.currentTarget.style.backgroundColor =
1246
- "#f0fdf4";
1247
- }
1248
- }
1335
+ opacity: isLoading ? 0.5 : 1,
1336
+ flexShrink: 0,
1249
1337
  }}
1338
+ onClick={(e) => e.stopPropagation()}
1250
1339
  >
1251
- {isLoading ? (
1252
- <div
1340
+ <input
1341
+ type="checkbox"
1342
+ checked={isActive}
1343
+ disabled={isLoading}
1344
+ onChange={() =>
1345
+ handleModelToggle(modelData.id, !isActive)
1346
+ }
1347
+ style={{
1348
+ opacity: 0,
1349
+ width: 0,
1350
+ height: 0,
1351
+ position: "absolute",
1352
+ }}
1353
+ />
1354
+ <span
1355
+ style={{
1356
+ position: "absolute",
1357
+ cursor: isLoading ? "not-allowed" : "pointer",
1358
+ top: 0,
1359
+ left: 0,
1360
+ right: 0,
1361
+ bottom: 0,
1362
+ backgroundColor: isActive ? "#10b981" : "#4b5563",
1363
+ transition: "0.3s",
1364
+ borderRadius: "28px",
1365
+ boxShadow: isActive
1366
+ ? "0 0 12px rgba(16, 185, 129, 0.4), inset 0 1px 3px rgba(0, 0, 0, 0.2)"
1367
+ : "inset 0 1px 3px rgba(0, 0, 0, 0.3)",
1368
+ border: isActive
1369
+ ? "2px solid #059669"
1370
+ : "2px solid #374151",
1371
+ }}
1372
+ >
1373
+ <span
1253
1374
  style={{
1254
- display: "flex",
1255
- alignItems: "center",
1256
- justifyContent: "center",
1257
- gap: "4px",
1375
+ position: "absolute",
1376
+ content: "",
1377
+ height: "22px",
1378
+ width: "22px",
1379
+ left: isActive ? "28px" : "2px",
1380
+ bottom: "2px",
1381
+ backgroundColor: "white",
1382
+ transition: "0.3s cubic-bezier(0.4, 0, 0.2, 1)",
1383
+ borderRadius: "50%",
1384
+ boxShadow: isActive
1385
+ ? "0 3px 8px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(16, 185, 129, 0.1)"
1386
+ : "0 3px 8px rgba(0, 0, 0, 0.3)",
1258
1387
  }}
1259
- >
1260
- <div
1261
- style={{
1262
- width: "12px",
1263
- height: "12px",
1264
- border: "2px solid currentColor",
1265
- borderTop: "2px solid transparent",
1266
- borderRadius: "50%",
1267
- animation: "ai-spin 1s linear infinite",
1268
- }}
1269
- />
1270
- </div>
1271
- ) : isActive ? (
1272
- "Désactiver"
1273
- ) : (
1274
- "Activer"
1275
- )}
1276
- </button>
1388
+ />
1389
+ </span>
1390
+ </label>
1277
1391
  </div>
1278
1392
  );
1279
1393
  })}
1394
+ {effectiveAvailableModels.filter((model) => {
1395
+ if (model.category !== modelCategory) return false;
1396
+ if (!modelSearchQuery.trim()) return true;
1397
+ const query = modelSearchQuery.toLowerCase();
1398
+ return (
1399
+ model.name.toLowerCase().includes(query) ||
1400
+ model.provider.toLowerCase().includes(query) ||
1401
+ model.description?.toLowerCase().includes(query)
1402
+ );
1403
+ }).length === 0 && (
1404
+ <div
1405
+ style={{
1406
+ textAlign: "center",
1407
+ padding: "32px 16px",
1408
+ color: "var(--ai-text-tertiary, #9ca3af)",
1409
+ fontSize: "14px",
1410
+ }}
1411
+ >
1412
+ {modelSearchQuery.trim()
1413
+ ? "Aucun modèle ne correspond à votre recherche"
1414
+ : "Aucun modèle disponible"}
1415
+ </div>
1416
+ )}
1280
1417
  </div>
1281
1418
  </div>
1282
1419
 
1283
1420
  <div style={aiStyles.modalFooter}>
1284
1421
  <button
1285
- onClick={() => setIsModelManagementOpen(false)}
1422
+ onClick={() => {
1423
+ setIsModelManagementOpen(false);
1424
+ setModelSearchQuery("");
1425
+ }}
1286
1426
  style={{
1287
1427
  ...aiStyles.button,
1288
1428
  ...aiStyles.buttonSecondary,