@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.
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiChipLabel.js +10 -7
- package/dist/components/AiContextButton.d.ts +1 -1
- package/dist/components/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +25 -12
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +32 -16
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +15 -5
- package/dist/components/AiModelSelect.d.ts.map +1 -1
- package/dist/components/AiModelSelect.js +3 -1
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +72 -47
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +8 -3
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +55 -28
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +19 -6
- package/dist/components/ErrorToast.d.ts.map +1 -1
- package/dist/components/ErrorToast.js +4 -2
- package/dist/components/LBApiKeySelector.d.ts.map +1 -1
- package/dist/components/LBApiKeySelector.js +13 -5
- package/dist/components/LBConnectButton.d.ts.map +1 -1
- package/dist/components/LBConnectButton.js +8 -3
- package/dist/components/LBKeyPicker.d.ts.map +1 -1
- package/dist/components/LBKeyPicker.js +8 -4
- package/dist/components/LBSigninModal.d.ts.map +1 -1
- package/dist/components/LBSigninModal.js +13 -7
- package/dist/components/UsageToast.d.ts.map +1 -1
- package/dist/components/UsageToast.js +4 -2
- package/dist/context/I18nContext.d.ts +15 -0
- package/dist/context/I18nContext.d.ts.map +1 -0
- package/dist/context/I18nContext.js +44 -0
- package/dist/context/LBAuthProvider.d.ts +4 -1
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +3 -2
- package/dist/hooks/useAiCallImage.d.ts.map +1 -1
- package/dist/hooks/useAiCallImage.js +1 -107
- package/dist/hooks/useAiCallText.d.ts.map +1 -1
- package/dist/hooks/useAiCallText.js +1 -25
- package/dist/hooks/useLoadingTimer.d.ts +5 -0
- package/dist/hooks/useLoadingTimer.d.ts.map +1 -0
- package/dist/hooks/useLoadingTimer.js +27 -0
- package/dist/i18n/de.json +62 -0
- package/dist/i18n/en.json +128 -0
- package/dist/i18n/es.json +70 -0
- package/dist/i18n/fr.json +128 -0
- package/dist/i18n/it.json +62 -0
- package/dist/i18n/pt.json +62 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/styles.css +142 -1
- package/package.json +3 -3
- package/src/components/AiChipLabel.tsx +17 -8
- package/src/components/AiContextButton.tsx +44 -20
- package/src/components/AiImageButton.tsx +52 -25
- package/src/components/AiInput.tsx +20 -5
- package/src/components/AiModelSelect.tsx +3 -1
- package/src/components/AiPromptPanel.tsx +177 -59
- package/src/components/AiSelect.tsx +8 -3
- package/src/components/AiStatusButton.tsx +100 -57
- package/src/components/AiTextarea.tsx +24 -6
- package/src/components/ErrorToast.tsx +4 -2
- package/src/components/LBApiKeySelector.tsx +33 -13
- package/src/components/LBConnectButton.tsx +9 -3
- package/src/components/LBKeyPicker.tsx +10 -4
- package/src/components/LBSigninModal.tsx +31 -15
- package/src/components/UsageToast.tsx +4 -2
- package/src/context/I18nContext.tsx +71 -0
- package/src/context/LBAuthProvider.tsx +9 -1
- package/src/hooks/useAiCallImage.ts +1 -149
- package/src/hooks/useAiCallText.ts +1 -30
- package/src/hooks/useLoadingTimer.ts +32 -0
- package/src/i18n/de.json +62 -0
- package/src/i18n/en.json +128 -0
- package/src/i18n/es.json +70 -0
- package/src/i18n/fr.json +128 -0
- package/src/i18n/it.json +62 -0
- package/src/i18n/pt.json +62 -0
- package/src/index.ts +2 -0
- 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 {
|
|
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
|
-
|
|
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
|
|
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}>
|
|
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
|
-
{
|
|
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=
|
|
578
|
+
title={t(
|
|
579
|
+
"prompt.modal.manageModels",
|
|
580
|
+
"Manage models"
|
|
581
|
+
)}
|
|
554
582
|
>
|
|
555
583
|
<Settings size={14} />
|
|
556
|
-
|
|
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
|
-
?
|
|
601
|
-
|
|
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
|
|
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
|
-
💡
|
|
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="">
|
|
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
|
-
(
|
|
724
|
+
{t(
|
|
725
|
+
"prompt.modal.promptHint",
|
|
726
|
+
"(Cmd/Ctrl + Enter to submit)"
|
|
727
|
+
)}
|
|
683
728
|
</span>
|
|
684
729
|
</label>
|
|
685
|
-
{
|
|
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 (
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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=
|
|
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
|
|
826
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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}>
|
|
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
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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=
|
|
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
|
-
{
|
|
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
|
-
?
|
|
1276
|
-
|
|
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
|
-
|
|
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?.({
|
|
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
|
-
? "
|
|
144
|
-
: "
|
|
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"
|