@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.
- 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 +23 -20
- 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 +141 -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 +51 -40
- 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 +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(
|
|
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 || "
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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="
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
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
|
-
|
|
235
|
+
{t("auth.modal.connecting", "Signing in...")}
|
|
220
236
|
</>
|
|
221
237
|
) : (
|
|
222
238
|
<>
|
|
223
239
|
<Sparkles size={16} />
|
|
224
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
|
package/src/i18n/de.json
ADDED
|
@@ -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
|
+
}
|