@lastbrain/ai-ui-react 1.0.73 → 1.0.75
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 +26 -12
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +35 -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 +6 -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 +31 -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 +14 -8
- package/src/components/AiContextButton.tsx +53 -20
- package/src/components/AiImageButton.tsx +58 -25
- package/src/components/AiInput.tsx +20 -5
- package/src/components/AiModelSelect.tsx +5 -1
- package/src/components/AiPromptPanel.tsx +203 -76
- package/src/components/AiSelect.tsx +8 -3
- package/src/components/AiStatusButton.tsx +75 -46
- package/src/components/AiTextarea.tsx +24 -6
- package/src/components/ErrorToast.tsx +4 -2
- package/src/components/LBApiKeySelector.tsx +29 -9
- package/src/components/LBConnectButton.tsx +7 -3
- package/src/components/LBKeyPicker.tsx +10 -4
- package/src/components/LBSigninModal.tsx +33 -15
- package/src/components/UsageToast.tsx +4 -2
- package/src/context/I18nContext.tsx +75 -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 +38 -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,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
if (!fetchApiKeys || !result.accessToken) {
|
|
62
|
-
setError("
|
|
67
|
+
setError(t("auth.modal.tokenMissing", "Access token unavailable"));
|
|
63
68
|
return;
|
|
64
69
|
}
|
|
65
70
|
|
|
@@ -69,11 +74,13 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
69
74
|
setShowKeySelector(true);
|
|
70
75
|
} catch (keyError) {
|
|
71
76
|
console.error("Failed to fetch API keys:", keyError);
|
|
72
|
-
setError("
|
|
77
|
+
setError(t("auth.modal.apiKeysFetchError", "Failed to fetch API keys"));
|
|
73
78
|
}
|
|
74
79
|
} catch (err) {
|
|
75
80
|
setError(
|
|
76
|
-
err instanceof Error
|
|
81
|
+
err instanceof Error
|
|
82
|
+
? err.message
|
|
83
|
+
: t("auth.modal.genericError", "An error occurred")
|
|
77
84
|
);
|
|
78
85
|
} finally {
|
|
79
86
|
setLoading(false);
|
|
@@ -91,7 +98,9 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
91
98
|
onClose();
|
|
92
99
|
} catch (err) {
|
|
93
100
|
setError(
|
|
94
|
-
err instanceof Error
|
|
101
|
+
err instanceof Error
|
|
102
|
+
? err.message
|
|
103
|
+
: t("auth.modal.selectionError", "Selection error")
|
|
95
104
|
);
|
|
96
105
|
setShowKeySelector(false);
|
|
97
106
|
}
|
|
@@ -132,7 +141,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
132
141
|
type="button"
|
|
133
142
|
className="ai-icon-btn ai-signin-close"
|
|
134
143
|
onClick={onClose}
|
|
135
|
-
aria-label="
|
|
144
|
+
aria-label={t("common.close", "Close")}
|
|
136
145
|
>
|
|
137
146
|
<X size={16} />
|
|
138
147
|
</button>
|
|
@@ -145,7 +154,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
145
154
|
</div>
|
|
146
155
|
<h2 className="ai-signin-title">{panelTitle}</h2>
|
|
147
156
|
<p className="ai-signin-subtitle">
|
|
148
|
-
|
|
157
|
+
{t(
|
|
158
|
+
"auth.modal.subtitle",
|
|
159
|
+
"Sign in to enable AI components in your app."
|
|
160
|
+
)}
|
|
149
161
|
</p>
|
|
150
162
|
</div>
|
|
151
163
|
|
|
@@ -157,7 +169,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
157
169
|
className="ai-input-label ai-row"
|
|
158
170
|
>
|
|
159
171
|
<Mail size={14} className="ai-inline-icon" />
|
|
160
|
-
Email
|
|
172
|
+
{t("auth.modal.email", "Email")}
|
|
161
173
|
</label>
|
|
162
174
|
<div className="ai-control-group ai-glow">
|
|
163
175
|
<div className="ai-shell ai-size-md ai-radius-full">
|
|
@@ -170,7 +182,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
170
182
|
required
|
|
171
183
|
autoFocus
|
|
172
184
|
autoComplete="email"
|
|
173
|
-
placeholder=
|
|
185
|
+
placeholder={t(
|
|
186
|
+
"auth.modal.emailPlaceholder",
|
|
187
|
+
"your@email.com"
|
|
188
|
+
)}
|
|
174
189
|
/>
|
|
175
190
|
</div>
|
|
176
191
|
</div>
|
|
@@ -182,7 +197,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
182
197
|
className="ai-input-label ai-row"
|
|
183
198
|
>
|
|
184
199
|
<Lock size={14} className="ai-inline-icon" />
|
|
185
|
-
|
|
200
|
+
{t("auth.modal.password", "Password")}
|
|
186
201
|
</label>
|
|
187
202
|
<div className="ai-control-group ai-glow">
|
|
188
203
|
<div className="ai-shell ai-size-md ai-radius-full">
|
|
@@ -194,7 +209,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
194
209
|
onChange={(e) => setPassword(e.target.value)}
|
|
195
210
|
required
|
|
196
211
|
autoComplete="current-password"
|
|
197
|
-
placeholder=
|
|
212
|
+
placeholder={t(
|
|
213
|
+
"auth.modal.passwordPlaceholder",
|
|
214
|
+
"••••••••"
|
|
215
|
+
)}
|
|
198
216
|
/>
|
|
199
217
|
</div>
|
|
200
218
|
</div>
|
|
@@ -216,12 +234,12 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
216
234
|
{loading ? (
|
|
217
235
|
<>
|
|
218
236
|
<Loader2 size={16} className="ai-spinner" />
|
|
219
|
-
|
|
237
|
+
{t("auth.modal.connecting", "Signing in...")}
|
|
220
238
|
</>
|
|
221
239
|
) : (
|
|
222
240
|
<>
|
|
223
241
|
<Sparkles size={16} />
|
|
224
|
-
|
|
242
|
+
{t("auth.signIn", "Sign in")}
|
|
225
243
|
</>
|
|
226
244
|
)}
|
|
227
245
|
</button>
|
|
@@ -232,7 +250,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
232
250
|
rel="noopener noreferrer"
|
|
233
251
|
className="ai-btn ai-btn--ghost"
|
|
234
252
|
>
|
|
235
|
-
|
|
253
|
+
{t("auth.modal.createAccount", "Create account")}
|
|
236
254
|
</a>
|
|
237
255
|
</div>
|
|
238
256
|
</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,75 @@
|
|
|
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({
|
|
50
|
+
children,
|
|
51
|
+
lang = defaultLang,
|
|
52
|
+
}: I18nProviderProps) {
|
|
53
|
+
const safeLang: LBSupportedLang = dictionaries[lang] ? lang : defaultLang;
|
|
54
|
+
|
|
55
|
+
const value = useMemo<I18nContextValue>(() => {
|
|
56
|
+
const dict = dictionaries[safeLang] || dictionaries[defaultLang];
|
|
57
|
+
|
|
58
|
+
const t = (key: string, fallback?: string, params?: TranslateParams) => {
|
|
59
|
+
const template =
|
|
60
|
+
dict[key] || dictionaries[defaultLang][key] || fallback || key;
|
|
61
|
+
if (!params) return template;
|
|
62
|
+
return template.replace(/\{(\w+)\}/g, (_, p: string) =>
|
|
63
|
+
params[p] !== undefined ? String(params[p]) : `{${p}}`
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return { lang: safeLang, t };
|
|
68
|
+
}, [safeLang]);
|
|
69
|
+
|
|
70
|
+
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useI18n() {
|
|
74
|
+
return useContext(I18nContext);
|
|
75
|
+
}
|
|
@@ -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,38 @@
|
|
|
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
|
+
const timeoutId = window.setTimeout(() => setSeconds(0), 0);
|
|
18
|
+
return () => window.clearTimeout(timeoutId);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const resetId = window.setTimeout(() => setSeconds(0), 0);
|
|
22
|
+
const intervalId = window.setInterval(() => {
|
|
23
|
+
setSeconds((prev) => prev + 1);
|
|
24
|
+
}, 1000);
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
window.clearTimeout(resetId);
|
|
28
|
+
window.clearInterval(intervalId);
|
|
29
|
+
};
|
|
30
|
+
}, [active]);
|
|
31
|
+
|
|
32
|
+
const displaySeconds = active ? seconds : 0;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
seconds: displaySeconds,
|
|
36
|
+
formatted: formatElapsed(displaySeconds),
|
|
37
|
+
};
|
|
38
|
+
}
|
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
|
+
}
|