@lastbrain/ai-ui-react 1.0.72 → 1.0.74

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 (83) hide show
  1. package/dist/components/AiChipLabel.d.ts.map +1 -1
  2. package/dist/components/AiChipLabel.js +10 -7
  3. package/dist/components/AiContextButton.d.ts +1 -1
  4. package/dist/components/AiContextButton.d.ts.map +1 -1
  5. package/dist/components/AiContextButton.js +25 -12
  6. package/dist/components/AiImageButton.d.ts.map +1 -1
  7. package/dist/components/AiImageButton.js +32 -16
  8. package/dist/components/AiInput.d.ts.map +1 -1
  9. package/dist/components/AiInput.js +15 -5
  10. package/dist/components/AiModelSelect.d.ts.map +1 -1
  11. package/dist/components/AiModelSelect.js +3 -1
  12. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  13. package/dist/components/AiPromptPanel.js +72 -47
  14. package/dist/components/AiSelect.d.ts.map +1 -1
  15. package/dist/components/AiSelect.js +8 -3
  16. package/dist/components/AiStatusButton.d.ts.map +1 -1
  17. package/dist/components/AiStatusButton.js +55 -28
  18. package/dist/components/AiTextarea.d.ts.map +1 -1
  19. package/dist/components/AiTextarea.js +19 -6
  20. package/dist/components/ErrorToast.d.ts.map +1 -1
  21. package/dist/components/ErrorToast.js +4 -2
  22. package/dist/components/LBApiKeySelector.d.ts.map +1 -1
  23. package/dist/components/LBApiKeySelector.js +13 -5
  24. package/dist/components/LBConnectButton.d.ts.map +1 -1
  25. package/dist/components/LBConnectButton.js +8 -3
  26. package/dist/components/LBKeyPicker.d.ts.map +1 -1
  27. package/dist/components/LBKeyPicker.js +8 -4
  28. package/dist/components/LBSigninModal.d.ts.map +1 -1
  29. package/dist/components/LBSigninModal.js +13 -7
  30. package/dist/components/UsageToast.d.ts.map +1 -1
  31. package/dist/components/UsageToast.js +4 -2
  32. package/dist/context/I18nContext.d.ts +15 -0
  33. package/dist/context/I18nContext.d.ts.map +1 -0
  34. package/dist/context/I18nContext.js +44 -0
  35. package/dist/context/LBAuthProvider.d.ts +4 -1
  36. package/dist/context/LBAuthProvider.d.ts.map +1 -1
  37. package/dist/context/LBAuthProvider.js +3 -2
  38. package/dist/hooks/useAiCallImage.d.ts.map +1 -1
  39. package/dist/hooks/useAiCallImage.js +1 -107
  40. package/dist/hooks/useAiCallText.d.ts.map +1 -1
  41. package/dist/hooks/useAiCallText.js +1 -25
  42. package/dist/hooks/useLoadingTimer.d.ts +5 -0
  43. package/dist/hooks/useLoadingTimer.d.ts.map +1 -0
  44. package/dist/hooks/useLoadingTimer.js +27 -0
  45. package/dist/i18n/de.json +62 -0
  46. package/dist/i18n/en.json +128 -0
  47. package/dist/i18n/es.json +70 -0
  48. package/dist/i18n/fr.json +128 -0
  49. package/dist/i18n/it.json +62 -0
  50. package/dist/i18n/pt.json +62 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -0
  54. package/dist/styles.css +142 -1
  55. package/package.json +3 -3
  56. package/src/components/AiChipLabel.tsx +17 -8
  57. package/src/components/AiContextButton.tsx +44 -20
  58. package/src/components/AiImageButton.tsx +52 -25
  59. package/src/components/AiInput.tsx +20 -5
  60. package/src/components/AiModelSelect.tsx +3 -1
  61. package/src/components/AiPromptPanel.tsx +177 -59
  62. package/src/components/AiSelect.tsx +8 -3
  63. package/src/components/AiStatusButton.tsx +100 -57
  64. package/src/components/AiTextarea.tsx +24 -6
  65. package/src/components/ErrorToast.tsx +4 -2
  66. package/src/components/LBApiKeySelector.tsx +33 -13
  67. package/src/components/LBConnectButton.tsx +9 -3
  68. package/src/components/LBKeyPicker.tsx +10 -4
  69. package/src/components/LBSigninModal.tsx +31 -15
  70. package/src/components/UsageToast.tsx +4 -2
  71. package/src/context/I18nContext.tsx +71 -0
  72. package/src/context/LBAuthProvider.tsx +9 -1
  73. package/src/hooks/useAiCallImage.ts +1 -149
  74. package/src/hooks/useAiCallText.ts +1 -30
  75. package/src/hooks/useLoadingTimer.ts +32 -0
  76. package/src/i18n/de.json +62 -0
  77. package/src/i18n/en.json +128 -0
  78. package/src/i18n/es.json +70 -0
  79. package/src/i18n/fr.json +128 -0
  80. package/src/i18n/it.json +62 -0
  81. package/src/i18n/pt.json +62 -0
  82. package/src/index.ts +2 -0
  83. package/src/styles.css +142 -1
@@ -9,7 +9,15 @@ import {
9
9
  type ReactNode,
10
10
  } from "react";
11
11
  import { createPortal } from "react-dom";
12
- import { BookOpen, Search, Sparkles, Star, Tag, Settings } from "lucide-react";
12
+ import {
13
+ BookOpen,
14
+ Search,
15
+ Sparkles,
16
+ Star,
17
+ Tag,
18
+ Settings,
19
+ Loader2,
20
+ } from "lucide-react";
13
21
  import type { ModelRef } from "@lastbrain/ai-ui-core";
14
22
  import type { UiMode } from "../types";
15
23
  import { aiStyles } from "../styles/inline";
@@ -22,6 +30,8 @@ import {
22
30
  import { useModelManagement } from "../hooks/useModelManagement";
23
31
  import { type AIModel } from "../context/AiProvider";
24
32
  import { AiProvider, useAiContext } from "../context/AiProvider";
33
+ import { useI18n } from "../context/I18nContext";
34
+ import { useLoadingTimer } from "../hooks/useLoadingTimer";
25
35
 
26
36
  export interface AiPromptPanelProps {
27
37
  isOpen: boolean;
@@ -105,6 +115,7 @@ function AiPromptPanelInternal({
105
115
  baseUrl,
106
116
  showOnlyUserModels = false,
107
117
  }: AiPromptPanelProps) {
118
+ const { t } = useI18n();
108
119
  const [selectedModel, setSelectedModel] = useState("");
109
120
  const [prompt, setPrompt] = useState("");
110
121
  const [promptId, setPromptId] = useState<string | undefined>(undefined);
@@ -125,6 +136,7 @@ function AiPromptPanelInternal({
125
136
  const [isModelManagementOpen, setIsModelManagementOpen] = useState(false);
126
137
  const [loadingModels, setLoadingModels] = useState<string[]>([]);
127
138
  const [modelSearchQuery, setModelSearchQuery] = useState("");
139
+ const { formatted: loadingElapsed } = useLoadingTimer(isGenerating);
128
140
 
129
141
  const {
130
142
  prompts,
@@ -164,6 +176,8 @@ function AiPromptPanelInternal({
164
176
  const effectiveUserModels =
165
177
  userModels.length > 0 ? userModels : autoModelManagement.userModels;
166
178
  const effectiveToggleModel = onModelToggle || autoModelManagement.toggleModel;
179
+ const isModelsLoading =
180
+ autoModelManagement.loading && effectiveAvailableModels.length === 0;
167
181
 
168
182
  // Gestion des modèles
169
183
  const handleModelToggle = async (modelId: string, isActive: boolean) => {
@@ -187,7 +201,7 @@ function AiPromptPanelInternal({
187
201
  id: m.id,
188
202
  name: m.name,
189
203
  category: m.type === "image" ? ("image" as const) : ("text" as const),
190
- provider: "Unknown",
204
+ provider: t("common.unknown", "Unknown"),
191
205
  }));
192
206
  }
193
207
 
@@ -464,7 +478,12 @@ function AiPromptPanelInternal({
464
478
  letterSpacing: "0.02em",
465
479
  }}
466
480
  >
467
- Génération en cours…
481
+ {t("prompt.modal.generating", "Generating...")}
482
+ </div>
483
+ <div className="ai-loading-meta">
484
+ {t("ai.loading.elapsed", "{seconds}", {
485
+ seconds: loadingElapsed,
486
+ })}
468
487
  </div>
469
488
  </div>
470
489
  )}
@@ -479,7 +498,9 @@ function AiPromptPanelInternal({
479
498
  }}
480
499
  >
481
500
  <h2 style={aiStyles.modalTitle}>
482
- {showPromptLibrary ? "Select a Prompt" : "AI Prompt Configuration"}
501
+ {showPromptLibrary
502
+ ? t("prompt.modal.selectPrompt", "Select a Prompt")
503
+ : t("prompt.modal.title", "AI Prompt Configuration")}
483
504
  </h2>
484
505
  <button
485
506
  style={{
@@ -489,7 +510,7 @@ function AiPromptPanelInternal({
489
510
  onClick={handleClose}
490
511
  onMouseEnter={() => setIsCloseHovered(true)}
491
512
  onMouseLeave={() => setIsCloseHovered(false)}
492
- aria-label="Close"
513
+ aria-label={t("common.closeLabel", "Close")}
493
514
  >
494
515
  ×
495
516
  </button>
@@ -511,7 +532,9 @@ function AiPromptPanelInternal({
511
532
  marginBottom: "16px",
512
533
  }}
513
534
  >
514
- <label style={aiStyles.modalLabel}>Source Text</label>
535
+ <label style={aiStyles.modalLabel}>
536
+ {t("prompt.modal.sourceText", "Source Text")}
537
+ </label>
515
538
  <div
516
539
  style={{
517
540
  padding: "12px",
@@ -546,14 +569,19 @@ function AiPromptPanelInternal({
546
569
  <label htmlFor="model-select" style={aiStyles.modalLabel}>
547
570
  AI Model
548
571
  </label>
549
- {effectiveAvailableModels.length > 0 && (
572
+ {isModelsLoading ? (
573
+ <span className="ai-inline-skeleton" aria-hidden="true" />
574
+ ) : effectiveAvailableModels.length > 0 ? (
550
575
  <button
551
576
  onClick={() => setIsModelManagementOpen(true)}
552
577
  className="ai-inline-btn"
553
- title="Gérer les modèles"
578
+ title={t(
579
+ "prompt.modal.manageModels",
580
+ "Manage models"
581
+ )}
554
582
  >
555
583
  <Settings size={14} />
556
- Gérer les modèles
584
+ {t("prompt.modal.manageModels", "Manage models")}
557
585
  {effectiveAvailableModels.filter(
558
586
  (m) =>
559
587
  m.category === modelCategory &&
@@ -581,7 +609,7 @@ function AiPromptPanelInternal({
581
609
  </span>
582
610
  )}
583
611
  </button>
584
- )}
612
+ ) : null}
585
613
  </div>
586
614
  <select
587
615
  id="model-select"
@@ -597,8 +625,14 @@ function AiPromptPanelInternal({
597
625
  {modelOptions.length === 0 && (
598
626
  <option value="">
599
627
  {showOnlyUserModels
600
- ? "No active models. Open 'Gérer les modèles'."
601
- : "Loading models..."}
628
+ ? t(
629
+ "prompt.modal.noActiveModels",
630
+ "No active models. Open 'Manage models'."
631
+ )
632
+ : t(
633
+ "prompt.modal.loadingModels",
634
+ "Loading models..."
635
+ )}
602
636
  </option>
603
637
  )}
604
638
  {modelOptions.map((model) => {
@@ -613,7 +647,9 @@ function AiPromptPanelInternal({
613
647
  >
614
648
  {model.name}
615
649
  {showAllModels && isActive && " ✓"}
616
- {showAllModels && !isActive && " (Désactivé)"}
650
+ {showAllModels && !isActive
651
+ ? ` (${t("common.inactive", "Inactive")})`
652
+ : ""}
617
653
  </option>
618
654
  );
619
655
  })}
@@ -626,7 +662,11 @@ function AiPromptPanelInternal({
626
662
  marginTop: "4px",
627
663
  }}
628
664
  >
629
- 💡 Cliquez sur "⚙️" pour activer/désactiver les modèles
665
+ 💡{" "}
666
+ {t(
667
+ "prompt.modal.clickSettingsHint",
668
+ "Click ⚙️ to enable/disable models"
669
+ )}
630
670
  </div>
631
671
  )}
632
672
  </div>
@@ -648,7 +688,9 @@ function AiPromptPanelInternal({
648
688
  }}
649
689
  >
650
690
  {models.length === 0 && (
651
- <option value="">Loading models...</option>
691
+ <option value="">
692
+ {t("prompt.modal.loadingModels", "Loading models...")}
693
+ </option>
652
694
  )}
653
695
  {models.map((model) => (
654
696
  <option key={model.id} value={model.id}>
@@ -670,7 +712,7 @@ function AiPromptPanelInternal({
670
712
  }}
671
713
  >
672
714
  <label htmlFor="prompt-input" style={aiStyles.modalLabel}>
673
- Prompt
715
+ {t("prompt.modal.prompt", "Prompt")}
674
716
  <span
675
717
  style={{
676
718
  color: "var(--ai-text-secondary)",
@@ -679,18 +721,24 @@ function AiPromptPanelInternal({
679
721
  fontWeight: 400,
680
722
  }}
681
723
  >
682
- (Cmd/Ctrl + Enter to submit)
724
+ {t(
725
+ "prompt.modal.promptHint",
726
+ "(Cmd/Ctrl + Enter to submit)"
727
+ )}
683
728
  </span>
684
729
  </label>
685
- {filteredPrompts.length > 0 && (
730
+ {promptsLoading ? (
731
+ <span className="ai-inline-skeleton" aria-hidden="true" />
732
+ ) : filteredPrompts.length > 0 ? (
686
733
  <button
687
734
  onClick={() => setShowPromptLibrary(true)}
688
735
  className="ai-inline-btn"
689
736
  >
690
737
  <BookOpen size={14} />
691
- Browse Prompts ({filteredPrompts.length})
738
+ {t("prompt.modal.browsePrompts", "Browse Prompts")} (
739
+ {filteredPrompts.length})
692
740
  </button>
693
- )}
741
+ ) : null}
694
742
  </div>
695
743
  <textarea
696
744
  id="prompt-input"
@@ -699,11 +747,17 @@ function AiPromptPanelInternal({
699
747
  onChange={(e) => setPrompt(e.target.value)}
700
748
  onFocus={() => setPromptFocused(true)}
701
749
  onBlur={() => setPromptFocused(false)}
702
- placeholder={
703
- sourceText
704
- ? "Enter your AI prompt... e.g., 'Correct spelling and grammar', 'Make it more professional', 'Translate to English'"
705
- : "Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'"
706
- }
750
+ placeholder={
751
+ sourceText
752
+ ? t(
753
+ "prompt.modal.promptPlaceholderWithSource",
754
+ "Enter your AI prompt... e.g., 'Fix grammar', 'Make it more professional', 'Translate to English'"
755
+ )
756
+ : t(
757
+ "prompt.modal.promptPlaceholderNoSource",
758
+ "Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'"
759
+ )
760
+ }
707
761
  rows={6}
708
762
  style={{
709
763
  ...aiStyles.textarea,
@@ -730,7 +784,7 @@ function AiPromptPanelInternal({
730
784
  gap: "4px",
731
785
  }}
732
786
  >
733
- ← Back to form
787
+ {t("prompt.modal.backToFormNoArrow", "Back to form")}
734
788
  </button>
735
789
 
736
790
  <div style={{ marginBottom: "12px" }}>
@@ -753,7 +807,10 @@ function AiPromptPanelInternal({
753
807
  <input
754
808
  value={searchQuery}
755
809
  onChange={(e) => setSearchQuery(e.target.value)}
756
- placeholder="Search prompts..."
810
+ placeholder={t(
811
+ "prompt.modal.searchPrompts",
812
+ "Search prompts..."
813
+ )}
757
814
  style={{
758
815
  ...aiStyles.input,
759
816
  padding: "10px 12px 10px 36px",
@@ -780,7 +837,7 @@ function AiPromptPanelInternal({
780
837
  }}
781
838
  >
782
839
  <Tag size={14} />
783
- Tags
840
+ {t("prompt.modal.tags", "Tags")}
784
841
  </span>
785
842
  <button
786
843
  onClick={() => setSelectedTag("all")}
@@ -796,7 +853,7 @@ function AiPromptPanelInternal({
796
853
  : (aiStyles.chip.background as string),
797
854
  }}
798
855
  >
799
- All
856
+ {t("common.all", "All")}
800
857
  </button>
801
858
  {availableTags.map((tag) => (
802
859
  <button
@@ -822,8 +879,21 @@ function AiPromptPanelInternal({
822
879
  </div>
823
880
 
824
881
  {promptsLoading ? (
825
- <div style={{ textAlign: "center", padding: "40px 0" }}>
826
- Loading prompts...
882
+ <div
883
+ style={{
884
+ display: "flex",
885
+ flexDirection: "column",
886
+ gap: "12px",
887
+ maxHeight: "400px",
888
+ overflow: "auto",
889
+ }}
890
+ >
891
+ {Array.from({ length: 4 }).map((_, idx) => (
892
+ <div key={`prompt-skeleton-${idx}`} className="ai-list-skeleton">
893
+ <div className="ai-list-skeleton__line ai-list-skeleton__line--lg" />
894
+ <div className="ai-list-skeleton__line ai-list-skeleton__line--md" />
895
+ </div>
896
+ ))}
827
897
  </div>
828
898
  ) : visiblePrompts.length === 0 ? (
829
899
  <div
@@ -833,7 +903,10 @@ function AiPromptPanelInternal({
833
903
  color: "var(--ai-text-secondary)",
834
904
  }}
835
905
  >
836
- No prompts available for this model type
906
+ {t(
907
+ "prompt.modal.noPrompts",
908
+ "No prompts available for this model type"
909
+ )}
837
910
  </div>
838
911
  ) : (
839
912
  <div
@@ -856,7 +929,7 @@ function AiPromptPanelInternal({
856
929
  marginBottom: "8px",
857
930
  }}
858
931
  >
859
- Favorites
932
+ {t("prompt.modal.favorites", "Favorites")}
860
933
  </div>
861
934
  <div
862
935
  style={{
@@ -944,7 +1017,7 @@ function AiPromptPanelInternal({
944
1017
  marginBottom: "8px",
945
1018
  }}
946
1019
  >
947
- All prompts
1020
+ {t("prompt.modal.allPrompts", "All prompts")}
948
1021
  </div>
949
1022
  <div
950
1023
  style={{
@@ -1031,14 +1104,34 @@ function AiPromptPanelInternal({
1031
1104
  }}
1032
1105
  >
1033
1106
  <button onClick={handleClose} className="ai-btn ai-btn--ghost">
1034
- Cancel
1107
+ {t("prompt.modal.cancel", "Cancel")}
1035
1108
  </button>
1036
1109
  <button
1037
1110
  onClick={handleSubmit}
1038
- disabled={!selectedModel || !prompt.trim()}
1111
+ disabled={isGenerating || !selectedModel || !prompt.trim()}
1039
1112
  className="ai-btn ai-btn--primary"
1040
1113
  >
1041
- {sourceText ? "Transform with AI" : "Generate with AI"}
1114
+ {isGenerating ? (
1115
+ <span className="ai-loading-stack">
1116
+ <span className="ai-loading-row">
1117
+ <Loader2 size={16} className="ai-spinner" />
1118
+ <span>
1119
+ {sourceText
1120
+ ? t("prompt.modal.transforming", "Transforming...")
1121
+ : t("prompt.modal.generating", "Generating...")}
1122
+ </span>
1123
+ </span>
1124
+ <span className="ai-loading-meta">
1125
+ {t("ai.loading.elapsed", "{seconds}", {
1126
+ seconds: loadingElapsed,
1127
+ })}
1128
+ </span>
1129
+ </span>
1130
+ ) : sourceText ? (
1131
+ t("prompt.modal.transform", "Transform with AI")
1132
+ ) : (
1133
+ t("prompt.modal.generate", "Generate with AI")
1134
+ )}
1042
1135
  </button>
1043
1136
  </div>
1044
1137
  </div>
@@ -1073,14 +1166,16 @@ function AiPromptPanelInternal({
1073
1166
  }}
1074
1167
  >
1075
1168
  <div style={aiStyles.modalHeader}>
1076
- <h2 style={aiStyles.modalTitle}>Gestion des modèles IA</h2>
1169
+ <h2 style={aiStyles.modalTitle}>
1170
+ {t("prompt.modal.modelMgmtTitle", "AI Model Management")}
1171
+ </h2>
1077
1172
  <button
1078
1173
  style={aiStyles.modalCloseButton}
1079
1174
  onClick={() => {
1080
1175
  setIsModelManagementOpen(false);
1081
1176
  setModelSearchQuery("");
1082
1177
  }}
1083
- aria-label="Close"
1178
+ aria-label={t("common.closeLabel", "Close")}
1084
1179
  >
1085
1180
  ×
1086
1181
  </button>
@@ -1101,7 +1196,10 @@ function AiPromptPanelInternal({
1101
1196
  margin: "0 0 8px 0",
1102
1197
  }}
1103
1198
  >
1104
- Activez ou désactivez les modèles selon vos besoins
1199
+ {t(
1200
+ "prompt.modal.modelMgmtSubtitle",
1201
+ "Enable or disable models based on your needs"
1202
+ )}
1105
1203
  </p>
1106
1204
  <p
1107
1205
  style={{
@@ -1110,20 +1208,20 @@ function AiPromptPanelInternal({
1110
1208
  margin: "0 0 16px 0",
1111
1209
  }}
1112
1210
  >
1113
- {
1114
- effectiveAvailableModels.filter(
1115
- (m) => m.category === modelCategory
1116
- ).length
1117
- }{" "}
1118
- modèles disponibles •{" "}
1119
- {
1120
- effectiveUserModels.filter((id) =>
1121
- effectiveAvailableModels.some(
1122
- (m) => m.id === id && m.category === modelCategory
1123
- )
1124
- ).length
1125
- }{" "}
1126
- activés
1211
+ {t(
1212
+ "prompt.modal.modelAvailableCount",
1213
+ "{available} models available {active} enabled",
1214
+ {
1215
+ available: effectiveAvailableModels.filter(
1216
+ (m) => m.category === modelCategory
1217
+ ).length,
1218
+ active: effectiveUserModels.filter((id) =>
1219
+ effectiveAvailableModels.some(
1220
+ (m) => m.id === id && m.category === modelCategory
1221
+ )
1222
+ ).length,
1223
+ }
1224
+ )}
1127
1225
  </p>
1128
1226
  {/* Champ de recherche */}
1129
1227
  <div
@@ -1145,7 +1243,10 @@ function AiPromptPanelInternal({
1145
1243
  <input
1146
1244
  value={modelSearchQuery}
1147
1245
  onChange={(e) => setModelSearchQuery(e.target.value)}
1148
- placeholder="Rechercher un modèle..."
1246
+ placeholder={t(
1247
+ "prompt.modal.searchModel",
1248
+ "Search a model..."
1249
+ )}
1149
1250
  style={{
1150
1251
  ...aiStyles.input,
1151
1252
  padding: "10px 12px 10px 36px",
@@ -1156,6 +1257,16 @@ function AiPromptPanelInternal({
1156
1257
  </div>
1157
1258
 
1158
1259
  <div className="ai-model-mgmt-list">
1260
+ {isModelsLoading ? (
1261
+ <>
1262
+ {Array.from({ length: 4 }).map((_, idx) => (
1263
+ <div key={`model-skeleton-${idx}`} className="ai-list-skeleton">
1264
+ <div className="ai-list-skeleton__line ai-list-skeleton__line--lg" />
1265
+ <div className="ai-list-skeleton__line ai-list-skeleton__line--md" />
1266
+ </div>
1267
+ ))}
1268
+ </>
1269
+ ) : null}
1159
1270
  {effectiveAvailableModels
1160
1271
  .filter((model) => {
1161
1272
  if (model.category !== modelCategory) return false;
@@ -1253,7 +1364,8 @@ function AiPromptPanelInternal({
1253
1364
  </div>
1254
1365
  );
1255
1366
  })}
1256
- {effectiveAvailableModels.filter((model) => {
1367
+ {!isModelsLoading &&
1368
+ effectiveAvailableModels.filter((model) => {
1257
1369
  if (model.category !== modelCategory) return false;
1258
1370
  if (!modelSearchQuery.trim()) return true;
1259
1371
  const query = modelSearchQuery.toLowerCase();
@@ -1272,8 +1384,14 @@ function AiPromptPanelInternal({
1272
1384
  }}
1273
1385
  >
1274
1386
  {modelSearchQuery.trim()
1275
- ? "Aucun modèle ne correspond à votre recherche"
1276
- : "Aucun modèle disponible"}
1387
+ ? t(
1388
+ "prompt.modal.noModelMatch",
1389
+ "No model matches your search"
1390
+ )
1391
+ : t(
1392
+ "prompt.modal.noModelAvailable",
1393
+ "No models available"
1394
+ )}
1277
1395
  </div>
1278
1396
  )}
1279
1397
  </div>
@@ -1287,7 +1405,7 @@ function AiPromptPanelInternal({
1287
1405
  }}
1288
1406
  className="ai-btn ai-btn--ghost"
1289
1407
  >
1290
- Fermer
1408
+ {t("prompt.modal.closeModelMgmt", "Close")}
1291
1409
  </button>
1292
1410
  </div>
1293
1411
  </div>
@@ -12,6 +12,7 @@ import { LBSigninModal } from "./LBSigninModal";
12
12
  import { handleAIError } from "../utils/errorHandler";
13
13
  import { useLB } from "../context/LBAuthProvider";
14
14
  import { useAiContext } from "../context/AiProvider";
15
+ import { useI18n } from "../context/I18nContext";
15
16
 
16
17
  export interface AiSelectProps
17
18
  extends
@@ -41,6 +42,7 @@ export function AiSelect({
41
42
  children,
42
43
  ...selectProps
43
44
  }: AiSelectProps) {
45
+ const { t } = useI18n();
44
46
  const [isOpen, setIsOpen] = useState(false);
45
47
  const [showAuthModal, setShowAuthModal] = useState(false);
46
48
  const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
@@ -107,7 +109,10 @@ export function AiSelect({
107
109
 
108
110
  if (result.text) {
109
111
  onValue?.(result.text);
110
- onToast?.({ type: "success", message: "AI suggestion ready" });
112
+ onToast?.({
113
+ type: "success",
114
+ message: t("ai.select.suggestionReady", "AI suggestion ready"),
115
+ });
111
116
  showUsageToast(result);
112
117
  }
113
118
  } catch (error) {
@@ -140,8 +145,8 @@ export function AiSelect({
140
145
  onClick={handleOpenPanel}
141
146
  title={
142
147
  shouldShowSparkles
143
- ? "Générer avec l'IA"
144
- : "Se connecter pour utiliser l'IA"
148
+ ? t("ai.generate", "Generate with AI")
149
+ : t("auth.connectToUseAi", "Sign in to use AI")
145
150
  }
146
151
  disabled={disabled || loading}
147
152
  type="button"