@lastbrain/ai-ui-react 1.0.73 → 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 +23 -20
  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 +141 -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 +51 -40
  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 +141 -1
@@ -6,6 +6,7 @@ import { createPortal } from "react-dom";
6
6
  import { AlertCircle, Loader2, Lock, Mail, Sparkles, X } from "lucide-react";
7
7
  import { useLB } from "../context/LBAuthProvider";
8
8
  import { LBApiKeySelector } from "./LBApiKeySelector";
9
+ import { useI18n } from "../context/I18nContext";
9
10
 
10
11
  export interface LBSigninModalProps {
11
12
  isOpen: boolean;
@@ -14,6 +15,7 @@ export interface LBSigninModalProps {
14
15
 
15
16
  export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
16
17
  const lbContext = useLB();
18
+ const { t } = useI18n();
17
19
  const [portalRoot, setPortalRoot] = useState<HTMLElement | null>(null);
18
20
 
19
21
  const [email, setEmail] = useState("");
@@ -27,7 +29,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
27
29
 
28
30
  const canRender = Boolean(isOpen && lbContext && login);
29
31
 
30
- const panelTitle = useMemo(() => "Connexion LastBrain", []);
32
+ const panelTitle = useMemo(
33
+ () => t("auth.modal.title", "LastBrain Sign In"),
34
+ [t]
35
+ );
31
36
 
32
37
  useEffect(() => {
33
38
  setPortalRoot(document.body);
@@ -49,7 +54,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
49
54
  try {
50
55
  const result = await login(email, password);
51
56
  if (!result.success) {
52
- setError(result.error || "Échec de la connexion");
57
+ setError(result.error || t("auth.modal.loginFailed", "Login failed"));
53
58
  return;
54
59
  }
55
60
 
@@ -59,7 +64,9 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
59
64
  }
60
65
 
61
66
  if (!fetchApiKeys || !result.accessToken) {
62
- setError("Token d'accès non disponible");
67
+ setError(
68
+ t("auth.modal.tokenMissing", "Access token unavailable")
69
+ );
63
70
  return;
64
71
  }
65
72
 
@@ -69,11 +76,15 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
69
76
  setShowKeySelector(true);
70
77
  } catch (keyError) {
71
78
  console.error("Failed to fetch API keys:", keyError);
72
- setError("Erreur lors de la récupération des clés API");
79
+ setError(
80
+ t("auth.modal.apiKeysFetchError", "Failed to fetch API keys")
81
+ );
73
82
  }
74
83
  } catch (err) {
75
84
  setError(
76
- err instanceof Error ? err.message : "Une erreur s'est produite"
85
+ err instanceof Error
86
+ ? err.message
87
+ : t("auth.modal.genericError", "An error occurred")
77
88
  );
78
89
  } finally {
79
90
  setLoading(false);
@@ -91,7 +102,9 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
91
102
  onClose();
92
103
  } catch (err) {
93
104
  setError(
94
- err instanceof Error ? err.message : "Erreur lors de la sélection"
105
+ err instanceof Error
106
+ ? err.message
107
+ : t("auth.modal.selectionError", "Selection error")
95
108
  );
96
109
  setShowKeySelector(false);
97
110
  }
@@ -132,7 +145,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
132
145
  type="button"
133
146
  className="ai-icon-btn ai-signin-close"
134
147
  onClick={onClose}
135
- aria-label="Fermer"
148
+ aria-label={t("common.close", "Close")}
136
149
  >
137
150
  <X size={16} />
138
151
  </button>
@@ -145,7 +158,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
145
158
  </div>
146
159
  <h2 className="ai-signin-title">{panelTitle}</h2>
147
160
  <p className="ai-signin-subtitle">
148
- Connectez-vous pour activer les composants IA dans votre app.
161
+ {t(
162
+ "auth.modal.subtitle",
163
+ "Sign in to enable AI components in your app."
164
+ )}
149
165
  </p>
150
166
  </div>
151
167
 
@@ -157,7 +173,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
157
173
  className="ai-input-label ai-row"
158
174
  >
159
175
  <Mail size={14} className="ai-inline-icon" />
160
- Email
176
+ {t("auth.modal.email", "Email")}
161
177
  </label>
162
178
  <div className="ai-control-group ai-glow">
163
179
  <div className="ai-shell ai-size-md ai-radius-full">
@@ -170,7 +186,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
170
186
  required
171
187
  autoFocus
172
188
  autoComplete="email"
173
- placeholder="votre@email.com"
189
+ placeholder={t("auth.modal.emailPlaceholder", "your@email.com")}
174
190
  />
175
191
  </div>
176
192
  </div>
@@ -182,7 +198,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
182
198
  className="ai-input-label ai-row"
183
199
  >
184
200
  <Lock size={14} className="ai-inline-icon" />
185
- Mot de passe
201
+ {t("auth.modal.password", "Password")}
186
202
  </label>
187
203
  <div className="ai-control-group ai-glow">
188
204
  <div className="ai-shell ai-size-md ai-radius-full">
@@ -194,7 +210,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
194
210
  onChange={(e) => setPassword(e.target.value)}
195
211
  required
196
212
  autoComplete="current-password"
197
- placeholder="••••••••"
213
+ placeholder={t("auth.modal.passwordPlaceholder", "••••••••")}
198
214
  />
199
215
  </div>
200
216
  </div>
@@ -216,12 +232,12 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
216
232
  {loading ? (
217
233
  <>
218
234
  <Loader2 size={16} className="ai-spinner" />
219
- Connexion...
235
+ {t("auth.modal.connecting", "Signing in...")}
220
236
  </>
221
237
  ) : (
222
238
  <>
223
239
  <Sparkles size={16} />
224
- Se connecter
240
+ {t("auth.signIn", "Sign in")}
225
241
  </>
226
242
  )}
227
243
  </button>
@@ -232,7 +248,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
232
248
  rel="noopener noreferrer"
233
249
  className="ai-btn ai-btn--ghost"
234
250
  >
235
- Créer un compte
251
+ {t("auth.modal.createAccount", "Create account")}
236
252
  </a>
237
253
  </div>
238
254
  </form>
@@ -3,6 +3,7 @@
3
3
  import "../styles/register";
4
4
  import { useEffect, useRef, useState } from "react";
5
5
  import { X } from "lucide-react";
6
+ import { useI18n } from "../context/I18nContext";
6
7
 
7
8
  interface UsageToastProps {
8
9
  result: unknown;
@@ -15,6 +16,7 @@ export function UsageToast({
15
16
  position = "bottom-right",
16
17
  onComplete,
17
18
  }: UsageToastProps) {
19
+ const { t } = useI18n();
18
20
  const [isVisible, setIsVisible] = useState(false);
19
21
  const [isClosing, setIsClosing] = useState(false);
20
22
  const fadeTimeoutRef = useRef<number | null>(null);
@@ -83,7 +85,7 @@ export function UsageToast({
83
85
  // Remove trailing zeros
84
86
  formatted = parseFloat(formatted).toString();
85
87
 
86
- return `${formatted}$ used`;
88
+ return t("usage.toast.used", "{amount}$ used", { amount: formatted });
87
89
  };
88
90
 
89
91
  const message = extractUsageMessage(result);
@@ -154,7 +156,7 @@ export function UsageToast({
154
156
  onMouseLeave={(e) => {
155
157
  e.currentTarget.style.backgroundColor = "transparent";
156
158
  }}
157
- title="Close"
159
+ title={t("common.closeLabel", "Close")}
158
160
  >
159
161
  <X size={12} />
160
162
  </button>
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
4
+ import fr from "../i18n/fr.json";
5
+ import en from "../i18n/en.json";
6
+ import es from "../i18n/es.json";
7
+ import it from "../i18n/it.json";
8
+ import de from "../i18n/de.json";
9
+ import pt from "../i18n/pt.json";
10
+
11
+ export type LBSupportedLang = "fr" | "en" | "es" | "it" | "de" | "pt";
12
+
13
+ type Dictionary = Record<string, string>;
14
+
15
+ const dictionaries: Record<LBSupportedLang, Dictionary> = {
16
+ fr,
17
+ en,
18
+ es,
19
+ it,
20
+ de,
21
+ pt,
22
+ };
23
+
24
+ type TranslateParams = Record<string, string | number>;
25
+
26
+ interface I18nContextValue {
27
+ lang: LBSupportedLang;
28
+ t: (key: string, fallback?: string, params?: TranslateParams) => string;
29
+ }
30
+
31
+ const defaultLang: LBSupportedLang = "fr";
32
+
33
+ const I18nContext = createContext<I18nContextValue>({
34
+ lang: defaultLang,
35
+ t: (key, fallback, params) => {
36
+ const template = dictionaries[defaultLang][key] || fallback || key;
37
+ if (!params) return template;
38
+ return template.replace(/\{(\w+)\}/g, (_, p: string) =>
39
+ params[p] !== undefined ? String(params[p]) : `{${p}}`
40
+ );
41
+ },
42
+ });
43
+
44
+ export interface I18nProviderProps {
45
+ children: ReactNode;
46
+ lang?: LBSupportedLang;
47
+ }
48
+
49
+ export function I18nProvider({ children, lang = defaultLang }: I18nProviderProps) {
50
+ const safeLang: LBSupportedLang = dictionaries[lang] ? lang : defaultLang;
51
+
52
+ const value = useMemo<I18nContextValue>(() => {
53
+ const dict = dictionaries[safeLang] || dictionaries[defaultLang];
54
+
55
+ const t = (key: string, fallback?: string, params?: TranslateParams) => {
56
+ const template = dict[key] || dictionaries[defaultLang][key] || fallback || key;
57
+ if (!params) return template;
58
+ return template.replace(/\{(\w+)\}/g, (_, p: string) =>
59
+ params[p] !== undefined ? String(params[p]) : `{${p}}`
60
+ );
61
+ };
62
+
63
+ return { lang: safeLang, t };
64
+ }, [safeLang]);
65
+
66
+ return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
67
+ }
68
+
69
+ export function useI18n() {
70
+ return useContext(I18nContext);
71
+ }
@@ -22,6 +22,7 @@ import type {
22
22
  AiStatus,
23
23
  } from "@lastbrain/ai-ui-core";
24
24
  import { createLBClient } from "@lastbrain/ai-ui-core";
25
+ import { I18nProvider, type LBSupportedLang } from "./I18nContext";
25
26
 
26
27
  export interface ApiKeyUser {
27
28
  id?: string;
@@ -66,6 +67,8 @@ interface LBProviderProps {
66
67
  onStatusChange?: (status: LBAuthState["status"]) => void;
67
68
  /** Fonction appelée après signin/logout pour refresh les providers */
68
69
  onAuthChange?: () => void;
70
+ /** Langue UI globale des composants ai-ui-react */
71
+ lang?: LBSupportedLang;
69
72
  }
70
73
 
71
74
  interface LBContextValue extends LBAuthState {
@@ -124,6 +127,7 @@ export function LBProvider({
124
127
  proxyUrl = "/api/lastbrain",
125
128
  onStatusChange,
126
129
  onAuthChange,
130
+ lang = "fr",
127
131
  }: LBProviderProps) {
128
132
  const [state, setState] = useState<LBAuthState>({
129
133
  status: "loading",
@@ -643,7 +647,11 @@ export function LBProvider({
643
647
  isLoadingStorage,
644
648
  };
645
649
 
646
- return <LBContext.Provider value={value}>{children}</LBContext.Provider>;
650
+ return (
651
+ <I18nProvider lang={lang}>
652
+ <LBContext.Provider value={value}>{children}</LBContext.Provider>
653
+ </I18nProvider>
654
+ );
647
655
  }
648
656
 
649
657
  /**
@@ -4,129 +4,6 @@ import { useState, useCallback } from "react";
4
4
  import type { AiImageRequest, AiImageResponse } from "@lastbrain/ai-ui-core";
5
5
  import { useAiClient } from "./useAiClient";
6
6
 
7
- /**
8
- * Génère une image Canvas pour l'environnement de développement
9
- */
10
- async function generateDevImage(
11
- prompt: string,
12
- width: number,
13
- height: number
14
- ): Promise<string> {
15
- // Créer un canvas
16
- const canvas = document.createElement("canvas");
17
- const ctx = canvas.getContext("2d");
18
-
19
- if (!ctx) {
20
- throw new Error("Could not create canvas context");
21
- }
22
-
23
- canvas.width = width;
24
- canvas.height = height;
25
-
26
- // Générer des couleurs basées sur le prompt
27
- const promptHash = prompt
28
- .split("")
29
- .reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) % 255, 0);
30
-
31
- // Couleurs de base dégradées
32
- const colors = [
33
- `hsl(${(promptHash * 7) % 360}, 70%, 60%)`,
34
- `hsl(${(promptHash * 11) % 360}, 60%, 70%)`,
35
- `hsl(${(promptHash * 13) % 360}, 50%, 80%)`,
36
- ];
37
-
38
- // Créer un dégradé radial
39
- const gradient = ctx.createRadialGradient(
40
- width / 2,
41
- height / 2,
42
- 0,
43
- width / 2,
44
- height / 2,
45
- Math.max(width, height) / 2
46
- );
47
-
48
- gradient.addColorStop(0, colors[0]);
49
- gradient.addColorStop(0.5, colors[1]);
50
- gradient.addColorStop(1, colors[2]);
51
-
52
- // Remplir le fond
53
- ctx.fillStyle = gradient;
54
- ctx.fillRect(0, 0, width, height);
55
-
56
- // Ajouter des formes géométriques basées sur le prompt
57
- const shapes = prompt.split(" ").slice(0, 5);
58
-
59
- shapes.forEach((word, index) => {
60
- const wordHash = word
61
- .split("")
62
- .reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) % 1000, 0);
63
-
64
- ctx.save();
65
- ctx.globalAlpha = 0.3 + index * 0.1;
66
- ctx.fillStyle = `hsl(${(wordHash * 17) % 360}, 80%, 50%)`;
67
-
68
- // Formes différentes selon l'index
69
- const x = (wordHash % (width - 100)) + 50;
70
- const y = ((wordHash * 3) % (height - 100)) + 50;
71
- const size = 30 + (wordHash % 50);
72
-
73
- switch (index % 4) {
74
- case 0: // Cercle
75
- ctx.beginPath();
76
- ctx.arc(x, y, size, 0, Math.PI * 2);
77
- ctx.fill();
78
- break;
79
- case 1: // Carré
80
- ctx.fillRect(x - size / 2, y - size / 2, size, size);
81
- break;
82
- case 2: // Triangle
83
- ctx.beginPath();
84
- ctx.moveTo(x, y - size / 2);
85
- ctx.lineTo(x - size / 2, y + size / 2);
86
- ctx.lineTo(x + size / 2, y + size / 2);
87
- ctx.closePath();
88
- ctx.fill();
89
- break;
90
- case 3: // Losange
91
- ctx.save();
92
- ctx.translate(x, y);
93
- ctx.rotate((wordHash / 100) % (Math.PI * 2));
94
- ctx.fillRect(-size / 2, -size / 2, size, size);
95
- ctx.restore();
96
- break;
97
- }
98
- ctx.restore();
99
- });
100
-
101
- // Ajouter le texte du prompt en overlay
102
- ctx.save();
103
- ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
104
- ctx.font = `bold ${Math.min(width, height) / 30}px system-ui, -apple-system, sans-serif`;
105
- ctx.textAlign = "center";
106
- ctx.textBaseline = "middle";
107
-
108
- // Fond semi-transparent pour le texte
109
- const textMetrics = ctx.measureText(prompt);
110
- const textWidth = textMetrics.width;
111
- const textHeight = parseInt(ctx.font);
112
-
113
- ctx.fillStyle = "rgba(0, 0, 0, 0.6)";
114
- ctx.fillRect(
115
- width / 2 - textWidth / 2 - 20,
116
- height / 2 - textHeight / 2 - 10,
117
- textWidth + 40,
118
- textHeight + 20
119
- );
120
-
121
- // Texte du prompt
122
- ctx.fillStyle = "white";
123
- ctx.fillText(prompt, width / 2, height / 2);
124
- ctx.restore();
125
-
126
- // Convertir en data URL
127
- return canvas.toDataURL("image/png", 0.8);
128
- }
129
-
130
7
  export interface UseAiCallImageOptions {
131
8
  baseUrl?: string;
132
9
  apiKeyId?: string;
@@ -150,31 +27,6 @@ export function useAiCallImage(
150
27
  setLoading(true);
151
28
  setError(null);
152
29
  try {
153
- // Vérifier si on est en mode dev (API key contient "dev")
154
- if (options?.apiKeyId?.includes("dev")) {
155
- // Simulation complète pour l'environnement dev
156
- await new Promise((resolve) => setTimeout(resolve, 2000)); // Délai réaliste pour simuler l'API
157
-
158
- // Parse size format "1024x1024" or default to "1024x1024"
159
- const sizeMatch = request.size?.match(/(\d+)x(\d+)/);
160
- const width = sizeMatch ? parseInt(sizeMatch[1]) : 1024;
161
- const height = sizeMatch ? parseInt(sizeMatch[2]) : 1024;
162
-
163
- // Créer une image générée avec Canvas
164
- const imageUrl = await generateDevImage(
165
- request.prompt,
166
- width,
167
- height
168
- );
169
-
170
- return {
171
- requestId: `dev-img-${Date.now()}`,
172
- url: imageUrl,
173
- debitTokens: 30, // Coût simulé
174
- };
175
- }
176
-
177
- // Mode production : appel API normal
178
30
  const result = await client.generateImage(request);
179
31
  return result;
180
32
  } catch (err) {
@@ -186,7 +38,7 @@ export function useAiCallImage(
186
38
  setLoading(false);
187
39
  }
188
40
  },
189
- [client, options?.apiKeyId]
41
+ [client]
190
42
  );
191
43
 
192
44
  return {
@@ -27,35 +27,6 @@ export function useAiCallText(
27
27
  setLoading(true);
28
28
  setError(null);
29
29
  try {
30
- // Vérifier si on est en mode dev (API key contient "dev")
31
- if (options?.apiKeyId?.includes("dev")) {
32
- // Simulation pour l'environnement dev
33
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Délai pour simuler l'API
34
-
35
- let simulatedResult = "";
36
-
37
- // Si le prompt contient des mots-clés pour les chips
38
- if (
39
- request.prompt.toLowerCase().includes("tags") ||
40
- request.prompt.toLowerCase().includes("chip") ||
41
- request.prompt.toLowerCase().includes("virgule")
42
- ) {
43
- simulatedResult =
44
- "react, typescript, javascript, frontend, backend, api, development, web, mobile, database";
45
- } else {
46
- // Lorem ipsum pour les autres cas
47
- simulatedResult =
48
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.";
49
- }
50
-
51
- return {
52
- requestId: `dev-${Date.now()}`,
53
- text: simulatedResult,
54
- debitTokens: 150,
55
- };
56
- }
57
-
58
- // Mode production : appel API normal
59
30
  const result = await client.generateText(request);
60
31
  return result;
61
32
  } catch (err) {
@@ -67,7 +38,7 @@ export function useAiCallText(
67
38
  setLoading(false);
68
39
  }
69
40
  },
70
- [client, options?.apiKeyId]
41
+ [client]
71
42
  );
72
43
 
73
44
  return {
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+
5
+ function formatElapsed(seconds: number): string {
6
+ const mins = Math.floor(seconds / 60);
7
+ const secs = seconds % 60;
8
+ if (mins <= 0) return `${secs}s`;
9
+ return `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
10
+ }
11
+
12
+ export function useLoadingTimer(active: boolean) {
13
+ const [seconds, setSeconds] = useState(0);
14
+
15
+ useEffect(() => {
16
+ if (!active) {
17
+ setSeconds(0);
18
+ return;
19
+ }
20
+ setSeconds(0);
21
+ const id = window.setInterval(() => {
22
+ setSeconds((prev) => prev + 1);
23
+ }, 1000);
24
+ return () => window.clearInterval(id);
25
+ }, [active]);
26
+
27
+ return {
28
+ seconds,
29
+ formatted: formatElapsed(seconds),
30
+ };
31
+ }
32
+
@@ -0,0 +1,62 @@
1
+ {
2
+ "common.close": "Schließen",
3
+ "common.cancel": "Abbrechen",
4
+ "common.loading": "Lädt...",
5
+ "common.search": "Suchen...",
6
+ "common.all": "Alle",
7
+ "common.unknown": "Unbekannt",
8
+ "common.error": "Ein Fehler ist aufgetreten",
9
+ "common.inactive": "Inaktiv",
10
+ "common.active": "Aktiv",
11
+ "common.save": "Speichern",
12
+ "common.download": "Herunterladen",
13
+ "common.continue": "Weiter",
14
+ "common.errorTitle": "Fehler",
15
+ "auth.signIn": "Anmelden",
16
+ "auth.signOut": "Abmelden",
17
+ "auth.required": "Authentifizierung erforderlich",
18
+ "auth.connectRequired": "Verbindung erforderlich",
19
+ "auth.modal.title": "LastBrain Anmeldung",
20
+ "auth.modal.subtitle": "Melde dich an, um KI-Komponenten in deiner App zu aktivieren.",
21
+ "auth.modal.email": "E-Mail",
22
+ "auth.modal.password": "Passwort",
23
+ "auth.modal.connecting": "Anmeldung...",
24
+ "auth.modal.createAccount": "Konto erstellen",
25
+ "status.title": "API-Status",
26
+ "status.view": "Status anzeigen",
27
+ "status.selectApiKey": "API-Schlüssel wählen",
28
+ "status.user": "Benutzer",
29
+ "status.apiKey": "API-Schlüssel",
30
+ "status.env": "Umgebung",
31
+ "status.rateLimit": "Rate Limit",
32
+ "status.auth": "Auth",
33
+ "status.wallet": "Wallet",
34
+ "status.storage": "Speicher",
35
+ "status.total": "Gesamt",
36
+ "status.changeApiKey": "API-Schlüssel wechseln",
37
+ "status.dashboard": "Dashboard",
38
+ "status.history": "Verlauf",
39
+ "status.settings": "Einstellungen",
40
+ "status.prompts": "Prompts",
41
+ "status.folders": "Ordner",
42
+ "status.logout": "Abmelden",
43
+ "prompt.modal.title": "KI Prompt Konfiguration",
44
+ "prompt.modal.aiModel": "KI-Modell",
45
+ "prompt.modal.manageModels": "Modelle verwalten",
46
+ "prompt.modal.loadingModels": "Modelle werden geladen...",
47
+ "prompt.modal.prompt": "Prompt",
48
+ "prompt.modal.browsePrompts": "Prompts durchsuchen",
49
+ "prompt.modal.cancel": "Abbrechen",
50
+ "prompt.modal.generate": "Mit KI generieren",
51
+ "prompt.modal.generating": "Generierung...",
52
+ "prompt.modal.transforming": "Transformation...",
53
+ "ai.setup": "KI einrichten",
54
+ "ai.generate": "Mit KI generieren",
55
+ "ai.loading.elapsed": "{seconds}",
56
+ "ai.generationSuccess": "KI-Generierung erfolgreich",
57
+ "ai.generationError": "Textgenerierung fehlgeschlagen",
58
+ "ai.image.generate": "Bild generieren",
59
+ "ai.image.generating": "Generierung...",
60
+ "ai.image.generatedSuccess": "Bild erfolgreich generiert",
61
+ "usage.toast.used": "{amount}$ verwendet"
62
+ }