@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ErrorToast.d.ts","sourceRoot":"","sources":["../../src/components/ErrorToast.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ErrorToast.d.ts","sourceRoot":"","sources":["../../src/components/ErrorToast.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAK5B,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,eAAe;IACvB,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAyB,EACzB,UAAU,GACX,EAAE,eAAe,kDA6IjB;AAED,wBAAgB,aAAa;4BAII,cAAc;;;;EAgB9C"}
|
|
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import "../styles/register";
|
|
4
4
|
import { useEffect, useRef, useState } from "react";
|
|
5
5
|
import { X, AlertCircle } from "lucide-react";
|
|
6
|
+
import { useI18n } from "../context/I18nContext";
|
|
6
7
|
export function ErrorToast({ error, position = "bottom-right", onComplete, }) {
|
|
8
|
+
const { t } = useI18n();
|
|
7
9
|
const [isVisible, setIsVisible] = useState(false);
|
|
8
10
|
const [isClosing, setIsClosing] = useState(false);
|
|
9
11
|
const fadeTimeoutRef = useRef(null);
|
|
@@ -81,7 +83,7 @@ export function ErrorToast({ error, position = "bottom-right", onComplete, }) {
|
|
|
81
83
|
WebkitBackdropFilter: "blur(8px)",
|
|
82
84
|
maxWidth: "400px",
|
|
83
85
|
minWidth: "280px",
|
|
84
|
-
}, children: [_jsx(AlertCircle, { size: 16, style: { marginTop: "2px", flexShrink: 0 } }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsxs("div", { style: { fontWeight: 600, marginBottom: "2px" }, children: ["
|
|
86
|
+
}, children: [_jsx(AlertCircle, { size: 16, style: { marginTop: "2px", flexShrink: 0 } }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsxs("div", { style: { fontWeight: 600, marginBottom: "2px" }, children: [t("common.errorTitle", "Error"), error.code && (_jsxs("span", { style: {
|
|
85
87
|
marginLeft: "8px",
|
|
86
88
|
fontSize: "10px",
|
|
87
89
|
opacity: 0.7,
|
|
@@ -102,7 +104,7 @@ export function ErrorToast({ error, position = "bottom-right", onComplete, }) {
|
|
|
102
104
|
e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)";
|
|
103
105
|
}, onMouseLeave: (e) => {
|
|
104
106
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
105
|
-
}, title: "
|
|
107
|
+
}, title: t("common.closeLabel", "Close"), children: _jsx(X, { size: 14 }) })] }));
|
|
106
108
|
}
|
|
107
109
|
export function useErrorToast() {
|
|
108
110
|
const [errorData, setErrorData] = useState(null);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBApiKeySelector.d.ts","sourceRoot":"","sources":["../../src/components/LBApiKeySelector.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LBApiKeySelector.d.ts","sourceRoot":"","sources":["../../src/components/LBApiKeySelector.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,MAAM,GACP,EAAE,qBAAqB,kDAuJvB"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
3
|
import { useState } from "react";
|
|
3
4
|
import { CheckCircle2, KeyRound, Loader2, XCircle } from "lucide-react";
|
|
5
|
+
import { useI18n } from "../context/I18nContext";
|
|
4
6
|
export function LBApiKeySelector({ apiKeys, onSelect, onCancel, isOpen, }) {
|
|
7
|
+
const { t } = useI18n();
|
|
5
8
|
const [selectedKeyId, setSelectedKeyId] = useState(apiKeys.find((k) => k.isActive)?.id || apiKeys[0]?.id || "");
|
|
6
9
|
const [loading, setLoading] = useState(false);
|
|
7
10
|
const [error, setError] = useState("");
|
|
@@ -10,7 +13,7 @@ export function LBApiKeySelector({ apiKeys, onSelect, onCancel, isOpen, }) {
|
|
|
10
13
|
const handleSubmit = async (e) => {
|
|
11
14
|
e.preventDefault();
|
|
12
15
|
if (!selectedKeyId) {
|
|
13
|
-
setError("
|
|
16
|
+
setError(t("status.selectApiKey", "Select an API key"));
|
|
14
17
|
return;
|
|
15
18
|
}
|
|
16
19
|
setLoading(true);
|
|
@@ -19,13 +22,18 @@ export function LBApiKeySelector({ apiKeys, onSelect, onCancel, isOpen, }) {
|
|
|
19
22
|
await onSelect(selectedKeyId);
|
|
20
23
|
}
|
|
21
24
|
catch (err) {
|
|
22
|
-
setError(err instanceof Error
|
|
25
|
+
setError(err instanceof Error
|
|
26
|
+
? err.message
|
|
27
|
+
: t("auth.modal.selectionError", "Selection error"));
|
|
23
28
|
setLoading(false);
|
|
24
29
|
}
|
|
25
30
|
};
|
|
26
|
-
return (_jsx("div", { className: "ai-signin-overlay", onClick: onCancel, children: _jsxs("div", { className: "ai-signin-panel ai-key-modal-panel", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "ai-signin-header", children: [_jsx("div", { className: "ai-center mb-3", children: _jsx("span", { className: "ai-icon-badge", children: _jsx(KeyRound, { size: 20 }) }) }), _jsx("h2", { className: "ai-signin-title", children: "
|
|
31
|
+
return (_jsx("div", { className: "ai-signin-overlay", onClick: onCancel, children: _jsxs("div", { className: "ai-signin-panel ai-key-modal-panel", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "ai-signin-header", children: [_jsx("div", { className: "ai-center mb-3", children: _jsx("span", { className: "ai-icon-badge", children: _jsx(KeyRound, { size: 20 }) }) }), _jsx("h2", { className: "ai-signin-title", children: t("status.selectApiKey", "Select an API key") }), _jsx("p", { className: "ai-signin-subtitle", children: t("status.selectApiKeySubtitle", "Choose the API key to use for your AI requests.") })] }), _jsx("div", { className: "ai-signin-content", children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsx("div", { className: "ai-model-mgmt-list", style: { maxHeight: 300, overflowY: "auto", marginBottom: 16 }, children: apiKeys.map((key) => {
|
|
27
32
|
const isSelected = key.id === selectedKeyId;
|
|
28
33
|
const isActive = key.isActive;
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
const rawEnv = key.env;
|
|
35
|
+
const keyEnv = rawEnv === "dev" ? "DEV" : "PROD";
|
|
36
|
+
const isDev = rawEnv === "dev";
|
|
37
|
+
return (_jsxs("label", { className: `ai-model-item ${isSelected ? "ai-model-item--active" : ""} ${!isActive ? "ai-model-item--disabled" : ""}`, children: [_jsxs("div", { className: "ai-model-item-main", children: [_jsx("input", { type: "radio", name: "apiKey", value: key.id, checked: isSelected, disabled: !isActive, onChange: (e) => setSelectedKeyId(e.target.value), className: "ai-key-radio" }), _jsxs("div", { children: [_jsx("div", { className: "ai-model-item-title", children: key.name }), _jsxs("div", { className: "ai-model-item-meta", children: [_jsx("span", { children: key.keyPrefix || key.id.substring(0, 12) + "..." }), _jsx("span", { className: `ai-pill ai-pill--cost ${isDev ? "ai-pill--warning" : ""}`, style: { marginLeft: 8 }, children: keyEnv })] })] })] }), isActive ? (_jsxs("span", { className: "ai-pill ai-pill--cost", children: [_jsx(CheckCircle2, { size: 12 }), t("common.active", "Active")] })) : (_jsxs("span", { className: "ai-pill ai-pill--cost", children: [_jsx(XCircle, { size: 12 }), t("common.inactive", "Inactive")] }))] }, key.id));
|
|
38
|
+
}) }), error ? (_jsxs("div", { className: "ai-signin-error", role: "alert", children: [_jsx(XCircle, { size: 16 }), _jsx("span", { children: error })] })) : null, _jsxs("div", { className: "ai-signin-actions", children: [_jsx("button", { type: "button", onClick: onCancel, disabled: loading, className: "ai-btn ai-btn--ghost", children: t("common.cancel", "Cancel") }), _jsx("button", { type: "submit", disabled: loading || !selectedKeyId, className: "ai-btn ai-btn--primary", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 16, className: "ai-spinner" }), t("auth.modal.connecting", "Signing in...")] })) : (t("common.continue", "Continue")) })] })] }) })] }) }));
|
|
31
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBConnectButton.d.ts","sourceRoot":"","sources":["../../src/components/LBConnectButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"LBConnectButton.d.ts","sourceRoot":"","sources":["../../src/components/LBConnectButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAO5B,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,SAAc,EACd,WAAW,EACX,WAAW,GACZ,EAAE,oBAAoB,2CAuDtB;AAED;;;GAGG;AACH,UAAU,gBAAgB;IACxB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE,gBAAgB,2CAExD"}
|
|
@@ -5,7 +5,9 @@ import React from "react";
|
|
|
5
5
|
import { Loader2, LogIn, LogOut } from "lucide-react";
|
|
6
6
|
import { useLB } from "../context/LBAuthProvider";
|
|
7
7
|
import { LBSigninModal } from "./LBSigninModal";
|
|
8
|
-
|
|
8
|
+
import { useI18n } from "../context/I18nContext";
|
|
9
|
+
export function LBConnectButton({ label, className = "", onConnected, onOpenModal, }) {
|
|
10
|
+
const { t } = useI18n();
|
|
9
11
|
const { status, user, logout } = useLB();
|
|
10
12
|
const [showModal, setShowModal] = React.useState(false);
|
|
11
13
|
React.useEffect(() => {
|
|
@@ -22,8 +24,9 @@ export function LBConnectButton({ label = "Se connecter", className = "", onConn
|
|
|
22
24
|
setShowModal(true);
|
|
23
25
|
onOpenModal?.();
|
|
24
26
|
};
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
+
const connectLabel = label || t("auth.signIn", "Sign in");
|
|
28
|
+
const buttonLabel = status === "ready" && user ? t("auth.signOut", "Sign out") : connectLabel;
|
|
29
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: handleClick, className: className || "ai-btn ai-btn--auth", disabled: status === "loading", children: status === "loading" ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 16, className: "animate-spin" }), t("common.loading", "Loading...")] })) : status === "ready" && user ? (_jsxs(_Fragment, { children: [_jsx(LogOut, { size: 16 }), buttonLabel] })) : (_jsxs(_Fragment, { children: [_jsx(LogIn, { size: 16 }), buttonLabel] })) }), _jsx(LBSigninModal, { isOpen: showModal, onClose: () => setShowModal(false) })] }));
|
|
27
30
|
}
|
|
28
31
|
export function LBAuthModal({ onClose }) {
|
|
29
32
|
return _jsx(LBSigninModal, { isOpen: true, onClose: () => onClose(false) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBKeyPicker.d.ts","sourceRoot":"","sources":["../../src/components/LBKeyPicker.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"LBKeyPicker.d.ts","sourceRoot":"","sources":["../../src/components/LBKeyPicker.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,UAAU,gBAAgB;IACxB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;CACxC;AAED,wBAAgB,WAAW,CAAC,EAC1B,SAAc,EACd,YAAY,GACb,EAAE,gBAAgB,kDAgIlB"}
|
|
@@ -7,7 +7,9 @@ import "../styles/register";
|
|
|
7
7
|
*/
|
|
8
8
|
import { useEffect, useState } from "react";
|
|
9
9
|
import { useLB } from "../hooks/useLB";
|
|
10
|
+
import { useI18n } from "../context/I18nContext";
|
|
10
11
|
export function LBKeyPicker({ className = "", onKeyChanged, }) {
|
|
12
|
+
const { t } = useI18n();
|
|
11
13
|
const { status, selectedKey, fetchApiKeys, selectApiKey, accessToken } = useLB();
|
|
12
14
|
const [apiKeys, setApiKeys] = useState([]);
|
|
13
15
|
const [loading, setLoading] = useState(false);
|
|
@@ -27,7 +29,7 @@ export function LBKeyPicker({ className = "", onKeyChanged, }) {
|
|
|
27
29
|
setApiKeys(keys);
|
|
28
30
|
}
|
|
29
31
|
catch (err) {
|
|
30
|
-
setError("
|
|
32
|
+
setError(t("lb.keypicker.loadError", "Unable to load API keys"));
|
|
31
33
|
}
|
|
32
34
|
finally {
|
|
33
35
|
setLoading(false);
|
|
@@ -47,7 +49,9 @@ export function LBKeyPicker({ className = "", onKeyChanged, }) {
|
|
|
47
49
|
setShowDropdown(false);
|
|
48
50
|
}
|
|
49
51
|
catch (err) {
|
|
50
|
-
setError(err instanceof Error
|
|
52
|
+
setError(err instanceof Error
|
|
53
|
+
? err.message
|
|
54
|
+
: t("lb.keypicker.switchError", "Failed to switch API key"));
|
|
51
55
|
}
|
|
52
56
|
finally {
|
|
53
57
|
setLoading(false);
|
|
@@ -56,7 +60,7 @@ export function LBKeyPicker({ className = "", onKeyChanged, }) {
|
|
|
56
60
|
if (status !== "ready" || !selectedKey) {
|
|
57
61
|
return null;
|
|
58
62
|
}
|
|
59
|
-
return (_jsxs("div", { className: `relative ${className}`, children: [_jsxs("button", { onClick: () => setShowDropdown(!showDropdown), disabled: loading, className: "px-4 py-2 border rounded hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 flex items-center gap-2", children: [_jsxs("span", { className: "font-mono text-sm", children: [selectedKey.keyPrefix, "..."] }), _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), showDropdown && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setShowDropdown(false) }), _jsxs("div", { className: "absolute right-0 mt-2 w-64 bg-white dark:bg-gray-800 border rounded-lg shadow-lg z-20", children: [_jsxs("div", { className: "p-2", children: [_jsx("div", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 px-3 py-2", children: "
|
|
63
|
+
return (_jsxs("div", { className: `relative ${className}`, children: [_jsxs("button", { onClick: () => setShowDropdown(!showDropdown), disabled: loading, className: "px-4 py-2 border rounded hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 flex items-center gap-2", children: [_jsxs("span", { className: "font-mono text-sm", children: [selectedKey.keyPrefix, "..."] }), _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) })] }), showDropdown && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setShowDropdown(false) }), _jsxs("div", { className: "absolute right-0 mt-2 w-64 bg-white dark:bg-gray-800 border rounded-lg shadow-lg z-20", children: [_jsxs("div", { className: "p-2", children: [_jsx("div", { className: "text-sm font-medium text-gray-700 dark:text-gray-300 px-3 py-2", children: t("lb.keypicker.changeApiKey", "Switch API key") }), _jsx("div", { className: "space-y-1", children: apiKeys.map((key) => (_jsxs("button", { onClick: () => handleSelectKey(key.id), disabled: !key.isActive || loading || key.id === selectedKey.id, className: `w-full text-left px-3 py-2 rounded text-sm ${key.id === selectedKey.id
|
|
60
64
|
? "bg-blue-100 dark:bg-blue-900"
|
|
61
|
-
: "hover:bg-gray-100 dark:hover:bg-gray-700"} disabled:opacity-50 disabled:cursor-not-allowed`, children: [_jsx("div", { className: "font-medium", children: key.name }), _jsxs("div", { className: "text-xs text-gray-500 font-mono", children: [key.keyPrefix, "..."] }), !key.isActive && (_jsx("div", { className: "text-xs text-red-600", children: "Inactive" }))] }, key.id))) })] }), error && (_jsx("div", { className: "px-3 py-2 text-xs text-red-600 border-t", children: error }))] })] }))] }));
|
|
65
|
+
: "hover:bg-gray-100 dark:hover:bg-gray-700"} disabled:opacity-50 disabled:cursor-not-allowed`, children: [_jsx("div", { className: "font-medium", children: key.name }), _jsxs("div", { className: "text-xs text-gray-500 font-mono", children: [key.keyPrefix, "..."] }), !key.isActive && (_jsx("div", { className: "text-xs text-red-600", children: t("common.inactive", "Inactive") }))] }, key.id))) })] }), error && (_jsx("div", { className: "px-3 py-2 text-xs text-red-600 border-t", children: error }))] })] }))] }));
|
|
62
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBSigninModal.d.ts","sourceRoot":"","sources":["../../src/components/LBSigninModal.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"LBSigninModal.d.ts","sourceRoot":"","sources":["../../src/components/LBSigninModal.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAQ5B,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,sCAsPpE"}
|
|
@@ -6,8 +6,10 @@ 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
|
export function LBSigninModal({ isOpen, onClose }) {
|
|
10
11
|
const lbContext = useLB();
|
|
12
|
+
const { t } = useI18n();
|
|
11
13
|
const [portalRoot, setPortalRoot] = useState(null);
|
|
12
14
|
const [email, setEmail] = useState("");
|
|
13
15
|
const [password, setPassword] = useState("");
|
|
@@ -17,7 +19,7 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
17
19
|
const [currentApiKeys, setCurrentApiKeys] = useState([]);
|
|
18
20
|
const { login, selectApiKeyWithToken, fetchApiKeys } = lbContext || {};
|
|
19
21
|
const canRender = Boolean(isOpen && lbContext && login);
|
|
20
|
-
const panelTitle = useMemo(() => "
|
|
22
|
+
const panelTitle = useMemo(() => t("auth.modal.title", "LastBrain Sign In"), [t]);
|
|
21
23
|
useEffect(() => {
|
|
22
24
|
setPortalRoot(document.body);
|
|
23
25
|
}, []);
|
|
@@ -34,7 +36,7 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
34
36
|
try {
|
|
35
37
|
const result = await login(email, password);
|
|
36
38
|
if (!result.success) {
|
|
37
|
-
setError(result.error || "
|
|
39
|
+
setError(result.error || t("auth.modal.loginFailed", "Login failed"));
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
if (!result.needsKeySelection) {
|
|
@@ -42,7 +44,7 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
if (!fetchApiKeys || !result.accessToken) {
|
|
45
|
-
setError("
|
|
47
|
+
setError(t("auth.modal.tokenMissing", "Access token unavailable"));
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
48
50
|
try {
|
|
@@ -52,11 +54,13 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
52
54
|
}
|
|
53
55
|
catch (keyError) {
|
|
54
56
|
console.error("Failed to fetch API keys:", keyError);
|
|
55
|
-
setError("
|
|
57
|
+
setError(t("auth.modal.apiKeysFetchError", "Failed to fetch API keys"));
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
catch (err) {
|
|
59
|
-
setError(err instanceof Error
|
|
61
|
+
setError(err instanceof Error
|
|
62
|
+
? err.message
|
|
63
|
+
: t("auth.modal.genericError", "An error occurred"));
|
|
60
64
|
}
|
|
61
65
|
finally {
|
|
62
66
|
setLoading(false);
|
|
@@ -72,7 +76,9 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
72
76
|
onClose();
|
|
73
77
|
}
|
|
74
78
|
catch (err) {
|
|
75
|
-
setError(err instanceof Error
|
|
79
|
+
setError(err instanceof Error
|
|
80
|
+
? err.message
|
|
81
|
+
: t("auth.modal.selectionError", "Selection error"));
|
|
76
82
|
setShowKeySelector(false);
|
|
77
83
|
}
|
|
78
84
|
};
|
|
@@ -87,5 +93,5 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
87
93
|
if (e.key === "Escape") {
|
|
88
94
|
onClose();
|
|
89
95
|
}
|
|
90
|
-
}, role: "dialog", "aria-modal": "true", "aria-label": panelTitle, children: _jsxs("div", { className: "ai-signin-panel", onClick: (e) => e.stopPropagation(), children: [_jsx("button", { type: "button", className: "ai-icon-btn ai-signin-close", onClick: onClose, "aria-label": "
|
|
96
|
+
}, role: "dialog", "aria-modal": "true", "aria-label": panelTitle, children: _jsxs("div", { className: "ai-signin-panel", onClick: (e) => e.stopPropagation(), children: [_jsx("button", { type: "button", className: "ai-icon-btn ai-signin-close", onClick: onClose, "aria-label": t("common.close", "Close"), children: _jsx(X, { size: 16 }) }), _jsxs("div", { className: "ai-signin-header", children: [_jsx("div", { className: "ai-center mb-3", children: _jsx("span", { className: "ai-icon-badge", children: _jsx(Sparkles, { size: 22 }) }) }), _jsx("h2", { className: "ai-signin-title", children: panelTitle }), _jsx("p", { className: "ai-signin-subtitle", children: t("auth.modal.subtitle", "Sign in to enable AI components in your app.") })] }), _jsx("div", { className: "ai-signin-content", children: _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs("div", { className: "ai-input-row", children: [_jsxs("label", { htmlFor: "lb-signin-email", className: "ai-input-label ai-row", children: [_jsx(Mail, { size: 14, className: "ai-inline-icon" }), t("auth.modal.email", "Email")] }), _jsx("div", { className: "ai-control-group ai-glow", children: _jsx("div", { className: "ai-shell ai-size-md ai-radius-full", children: _jsx("input", { id: "lb-signin-email", className: "ai-control ai-control-input ai-size-md ai-radius-full", type: "email", value: email, onChange: (e) => setEmail(e.target.value), required: true, autoFocus: true, autoComplete: "email", placeholder: t("auth.modal.emailPlaceholder", "your@email.com") }) }) })] }), _jsxs("div", { className: "ai-input-row", children: [_jsxs("label", { htmlFor: "lb-signin-password", className: "ai-input-label ai-row", children: [_jsx(Lock, { size: 14, className: "ai-inline-icon" }), t("auth.modal.password", "Password")] }), _jsx("div", { className: "ai-control-group ai-glow", children: _jsx("div", { className: "ai-shell ai-size-md ai-radius-full", children: _jsx("input", { id: "lb-signin-password", className: "ai-control ai-control-input ai-size-md ai-radius-full", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, autoComplete: "current-password", placeholder: t("auth.modal.passwordPlaceholder", "••••••••") }) }) })] }), error ? (_jsxs("div", { className: "ai-signin-error", role: "alert", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] })) : null, _jsxs("div", { className: "ai-signin-actions", children: [_jsx("button", { type: "submit", className: "ai-btn ai-btn--auth", disabled: loading, children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 16, className: "ai-spinner" }), t("auth.modal.connecting", "Signing in...")] })) : (_jsxs(_Fragment, { children: [_jsx(Sparkles, { size: 16 }), t("auth.signIn", "Sign in")] })) }), _jsx("a", { href: "https://prompt.lastbrain.io/signup", target: "_blank", rel: "noopener noreferrer", className: "ai-btn ai-btn--ghost", children: t("auth.modal.createAccount", "Create account") })] })] }) })] }) }), portalRoot);
|
|
91
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UsageToast.d.ts","sourceRoot":"","sources":["../../src/components/UsageToast.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"UsageToast.d.ts","sourceRoot":"","sources":["../../src/components/UsageToast.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAK5B,UAAU,eAAe;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAyB,EACzB,UAAU,GACX,EAAE,eAAe,kDAmJjB;AAED,wBAAgB,aAAa;6BAIK,OAAO;;;;EAgBxC"}
|
|
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
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
|
export function UsageToast({ result, position = "bottom-right", onComplete, }) {
|
|
8
|
+
const { t } = useI18n();
|
|
7
9
|
const [isVisible, setIsVisible] = useState(false);
|
|
8
10
|
const [isClosing, setIsClosing] = useState(false);
|
|
9
11
|
const fadeTimeoutRef = useRef(null);
|
|
@@ -67,7 +69,7 @@ export function UsageToast({ result, position = "bottom-right", onComplete, }) {
|
|
|
67
69
|
}
|
|
68
70
|
// Remove trailing zeros
|
|
69
71
|
formatted = parseFloat(formatted).toString();
|
|
70
|
-
return
|
|
72
|
+
return t("usage.toast.used", "{amount}$ used", { amount: formatted });
|
|
71
73
|
};
|
|
72
74
|
const message = extractUsageMessage(result);
|
|
73
75
|
if (!result)
|
|
@@ -125,7 +127,7 @@ export function UsageToast({ result, position = "bottom-right", onComplete, }) {
|
|
|
125
127
|
e.currentTarget.style.backgroundColor = "rgba(22, 163, 74, 0.2)";
|
|
126
128
|
}, onMouseLeave: (e) => {
|
|
127
129
|
e.currentTarget.style.backgroundColor = "transparent";
|
|
128
|
-
}, title: "Close", children: _jsx(X, { size: 12 }) })] }));
|
|
130
|
+
}, title: t("common.closeLabel", "Close"), children: _jsx(X, { size: 12 }) })] }));
|
|
129
131
|
}
|
|
130
132
|
export function useUsageToast() {
|
|
131
133
|
const [toastData, setToastData] = useState(null);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type LBSupportedLang = "fr" | "en" | "es" | "it" | "de" | "pt";
|
|
3
|
+
type TranslateParams = Record<string, string | number>;
|
|
4
|
+
interface I18nContextValue {
|
|
5
|
+
lang: LBSupportedLang;
|
|
6
|
+
t: (key: string, fallback?: string, params?: TranslateParams) => string;
|
|
7
|
+
}
|
|
8
|
+
export interface I18nProviderProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
lang?: LBSupportedLang;
|
|
11
|
+
}
|
|
12
|
+
export declare function I18nProvider({ children, lang, }: I18nProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function useI18n(): I18nContextValue;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=I18nContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"I18nContext.d.ts","sourceRoot":"","sources":["../../src/context/I18nContext.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQ3E,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAatE,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,UAAU,gBAAgB;IACxB,IAAI,EAAE,eAAe,CAAC;IACtB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,eAAe,KAAK,MAAM,CAAC;CACzE;AAeD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,IAAkB,GACnB,EAAE,iBAAiB,2CAmBnB;AAED,wBAAgB,OAAO,qBAEtB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useMemo } 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
|
+
const dictionaries = {
|
|
11
|
+
fr,
|
|
12
|
+
en,
|
|
13
|
+
es,
|
|
14
|
+
it,
|
|
15
|
+
de,
|
|
16
|
+
pt,
|
|
17
|
+
};
|
|
18
|
+
const defaultLang = "fr";
|
|
19
|
+
const I18nContext = createContext({
|
|
20
|
+
lang: defaultLang,
|
|
21
|
+
t: (key, fallback, params) => {
|
|
22
|
+
const template = dictionaries[defaultLang][key] || fallback || key;
|
|
23
|
+
if (!params)
|
|
24
|
+
return template;
|
|
25
|
+
return template.replace(/\{(\w+)\}/g, (_, p) => params[p] !== undefined ? String(params[p]) : `{${p}}`);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
export function I18nProvider({ children, lang = defaultLang, }) {
|
|
29
|
+
const safeLang = dictionaries[lang] ? lang : defaultLang;
|
|
30
|
+
const value = useMemo(() => {
|
|
31
|
+
const dict = dictionaries[safeLang] || dictionaries[defaultLang];
|
|
32
|
+
const t = (key, fallback, params) => {
|
|
33
|
+
const template = dict[key] || dictionaries[defaultLang][key] || fallback || key;
|
|
34
|
+
if (!params)
|
|
35
|
+
return template;
|
|
36
|
+
return template.replace(/\{(\w+)\}/g, (_, p) => params[p] !== undefined ? String(params[p]) : `{${p}}`);
|
|
37
|
+
};
|
|
38
|
+
return { lang: safeLang, t };
|
|
39
|
+
}, [safeLang]);
|
|
40
|
+
return _jsx(I18nContext.Provider, { value: value, children: children });
|
|
41
|
+
}
|
|
42
|
+
export function useI18n() {
|
|
43
|
+
return useContext(I18nContext);
|
|
44
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { type ReactNode } from "react";
|
|
6
6
|
import type { LBAuthState, LBApiKey, AiStatus } from "@lastbrain/ai-ui-core";
|
|
7
|
+
import { type LBSupportedLang } from "./I18nContext";
|
|
7
8
|
export interface ApiKeyUser {
|
|
8
9
|
id?: string;
|
|
9
10
|
name?: string;
|
|
@@ -44,6 +45,8 @@ interface LBProviderProps {
|
|
|
44
45
|
onStatusChange?: (status: LBAuthState["status"]) => void;
|
|
45
46
|
/** Fonction appelée après signin/logout pour refresh les providers */
|
|
46
47
|
onAuthChange?: () => void;
|
|
48
|
+
/** Langue UI globale des composants ai-ui-react */
|
|
49
|
+
lang?: LBSupportedLang;
|
|
47
50
|
}
|
|
48
51
|
interface LBContextValue extends LBAuthState {
|
|
49
52
|
/** Fonction de connexion */
|
|
@@ -88,7 +91,7 @@ interface LBContextValue extends LBAuthState {
|
|
|
88
91
|
}
|
|
89
92
|
declare const LBContext: import("react").Context<LBContextValue | undefined>;
|
|
90
93
|
export { LBContext };
|
|
91
|
-
export declare function LBProvider({ children, baseUrl: _baseUrl, proxyUrl, onStatusChange, onAuthChange, }: LBProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
94
|
+
export declare function LBProvider({ children, baseUrl: _baseUrl, proxyUrl, onStatusChange, onAuthChange, lang, }: LBProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
92
95
|
/**
|
|
93
96
|
* Hook pour accéder au contexte LastBrain
|
|
94
97
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAGR,QAAQ,EACT,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"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"}
|
|
@@ -6,10 +6,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { createContext, useContext, useEffect, useCallback, useMemo, useState, } from "react";
|
|
8
8
|
import { createLBClient } from "@lastbrain/ai-ui-core";
|
|
9
|
+
import { I18nProvider } from "./I18nContext";
|
|
9
10
|
const LBContext = createContext(undefined);
|
|
10
11
|
// Export pour usage dans d'autres composants
|
|
11
12
|
export { LBContext };
|
|
12
|
-
export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", proxyUrl = "/api/lastbrain", onStatusChange, onAuthChange, }) {
|
|
13
|
+
export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", proxyUrl = "/api/lastbrain", onStatusChange, onAuthChange, lang = "fr", }) {
|
|
13
14
|
const [state, setState] = useState({
|
|
14
15
|
status: "loading",
|
|
15
16
|
});
|
|
@@ -453,7 +454,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
453
454
|
isLoadingStatus,
|
|
454
455
|
isLoadingStorage,
|
|
455
456
|
};
|
|
456
|
-
return _jsx(LBContext.Provider, { value: value, children: children });
|
|
457
|
+
return (_jsx(I18nProvider, { lang: lang, children: _jsx(LBContext.Provider, { value: value, children: children }) }));
|
|
457
458
|
}
|
|
458
459
|
/**
|
|
459
460
|
* Hook pour accéder au contexte LastBrain
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAiCallImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallImage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"useAiCallImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallImage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7E,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,oBAAoB,CA6BtB"}
|
|
@@ -1,95 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useState, useCallback } from "react";
|
|
3
3
|
import { useAiClient } from "./useAiClient";
|
|
4
|
-
/**
|
|
5
|
-
* Génère une image Canvas pour l'environnement de développement
|
|
6
|
-
*/
|
|
7
|
-
async function generateDevImage(prompt, width, height) {
|
|
8
|
-
// Créer un canvas
|
|
9
|
-
const canvas = document.createElement("canvas");
|
|
10
|
-
const ctx = canvas.getContext("2d");
|
|
11
|
-
if (!ctx) {
|
|
12
|
-
throw new Error("Could not create canvas context");
|
|
13
|
-
}
|
|
14
|
-
canvas.width = width;
|
|
15
|
-
canvas.height = height;
|
|
16
|
-
// Générer des couleurs basées sur le prompt
|
|
17
|
-
const promptHash = prompt
|
|
18
|
-
.split("")
|
|
19
|
-
.reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) % 255, 0);
|
|
20
|
-
// Couleurs de base dégradées
|
|
21
|
-
const colors = [
|
|
22
|
-
`hsl(${(promptHash * 7) % 360}, 70%, 60%)`,
|
|
23
|
-
`hsl(${(promptHash * 11) % 360}, 60%, 70%)`,
|
|
24
|
-
`hsl(${(promptHash * 13) % 360}, 50%, 80%)`,
|
|
25
|
-
];
|
|
26
|
-
// Créer un dégradé radial
|
|
27
|
-
const gradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, Math.max(width, height) / 2);
|
|
28
|
-
gradient.addColorStop(0, colors[0]);
|
|
29
|
-
gradient.addColorStop(0.5, colors[1]);
|
|
30
|
-
gradient.addColorStop(1, colors[2]);
|
|
31
|
-
// Remplir le fond
|
|
32
|
-
ctx.fillStyle = gradient;
|
|
33
|
-
ctx.fillRect(0, 0, width, height);
|
|
34
|
-
// Ajouter des formes géométriques basées sur le prompt
|
|
35
|
-
const shapes = prompt.split(" ").slice(0, 5);
|
|
36
|
-
shapes.forEach((word, index) => {
|
|
37
|
-
const wordHash = word
|
|
38
|
-
.split("")
|
|
39
|
-
.reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) % 1000, 0);
|
|
40
|
-
ctx.save();
|
|
41
|
-
ctx.globalAlpha = 0.3 + index * 0.1;
|
|
42
|
-
ctx.fillStyle = `hsl(${(wordHash * 17) % 360}, 80%, 50%)`;
|
|
43
|
-
// Formes différentes selon l'index
|
|
44
|
-
const x = (wordHash % (width - 100)) + 50;
|
|
45
|
-
const y = ((wordHash * 3) % (height - 100)) + 50;
|
|
46
|
-
const size = 30 + (wordHash % 50);
|
|
47
|
-
switch (index % 4) {
|
|
48
|
-
case 0: // Cercle
|
|
49
|
-
ctx.beginPath();
|
|
50
|
-
ctx.arc(x, y, size, 0, Math.PI * 2);
|
|
51
|
-
ctx.fill();
|
|
52
|
-
break;
|
|
53
|
-
case 1: // Carré
|
|
54
|
-
ctx.fillRect(x - size / 2, y - size / 2, size, size);
|
|
55
|
-
break;
|
|
56
|
-
case 2: // Triangle
|
|
57
|
-
ctx.beginPath();
|
|
58
|
-
ctx.moveTo(x, y - size / 2);
|
|
59
|
-
ctx.lineTo(x - size / 2, y + size / 2);
|
|
60
|
-
ctx.lineTo(x + size / 2, y + size / 2);
|
|
61
|
-
ctx.closePath();
|
|
62
|
-
ctx.fill();
|
|
63
|
-
break;
|
|
64
|
-
case 3: // Losange
|
|
65
|
-
ctx.save();
|
|
66
|
-
ctx.translate(x, y);
|
|
67
|
-
ctx.rotate((wordHash / 100) % (Math.PI * 2));
|
|
68
|
-
ctx.fillRect(-size / 2, -size / 2, size, size);
|
|
69
|
-
ctx.restore();
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
ctx.restore();
|
|
73
|
-
});
|
|
74
|
-
// Ajouter le texte du prompt en overlay
|
|
75
|
-
ctx.save();
|
|
76
|
-
ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
|
|
77
|
-
ctx.font = `bold ${Math.min(width, height) / 30}px system-ui, -apple-system, sans-serif`;
|
|
78
|
-
ctx.textAlign = "center";
|
|
79
|
-
ctx.textBaseline = "middle";
|
|
80
|
-
// Fond semi-transparent pour le texte
|
|
81
|
-
const textMetrics = ctx.measureText(prompt);
|
|
82
|
-
const textWidth = textMetrics.width;
|
|
83
|
-
const textHeight = parseInt(ctx.font);
|
|
84
|
-
ctx.fillStyle = "rgba(0, 0, 0, 0.6)";
|
|
85
|
-
ctx.fillRect(width / 2 - textWidth / 2 - 20, height / 2 - textHeight / 2 - 10, textWidth + 40, textHeight + 20);
|
|
86
|
-
// Texte du prompt
|
|
87
|
-
ctx.fillStyle = "white";
|
|
88
|
-
ctx.fillText(prompt, width / 2, height / 2);
|
|
89
|
-
ctx.restore();
|
|
90
|
-
// Convertir en data URL
|
|
91
|
-
return canvas.toDataURL("image/png", 0.8);
|
|
92
|
-
}
|
|
93
4
|
export function useAiCallImage(options) {
|
|
94
5
|
const client = useAiClient(options);
|
|
95
6
|
const [loading, setLoading] = useState(false);
|
|
@@ -98,23 +9,6 @@ export function useAiCallImage(options) {
|
|
|
98
9
|
setLoading(true);
|
|
99
10
|
setError(null);
|
|
100
11
|
try {
|
|
101
|
-
// Vérifier si on est en mode dev (API key contient "dev")
|
|
102
|
-
if (options?.apiKeyId?.includes("dev")) {
|
|
103
|
-
// Simulation complète pour l'environnement dev
|
|
104
|
-
await new Promise((resolve) => setTimeout(resolve, 2000)); // Délai réaliste pour simuler l'API
|
|
105
|
-
// Parse size format "1024x1024" or default to "1024x1024"
|
|
106
|
-
const sizeMatch = request.size?.match(/(\d+)x(\d+)/);
|
|
107
|
-
const width = sizeMatch ? parseInt(sizeMatch[1]) : 1024;
|
|
108
|
-
const height = sizeMatch ? parseInt(sizeMatch[2]) : 1024;
|
|
109
|
-
// Créer une image générée avec Canvas
|
|
110
|
-
const imageUrl = await generateDevImage(request.prompt, width, height);
|
|
111
|
-
return {
|
|
112
|
-
requestId: `dev-img-${Date.now()}`,
|
|
113
|
-
url: imageUrl,
|
|
114
|
-
debitTokens: 30, // Coût simulé
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
// Mode production : appel API normal
|
|
118
12
|
const result = await client.generateImage(request);
|
|
119
13
|
return result;
|
|
120
14
|
}
|
|
@@ -126,7 +20,7 @@ export function useAiCallImage(options) {
|
|
|
126
20
|
finally {
|
|
127
21
|
setLoading(false);
|
|
128
22
|
}
|
|
129
|
-
}, [client
|
|
23
|
+
}, [client]);
|
|
130
24
|
return {
|
|
131
25
|
generateImage,
|
|
132
26
|
loading,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAiCallText.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallText.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,
|
|
1
|
+
{"version":3,"file":"useAiCallText.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallText.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CA6BrB"}
|
|
@@ -9,30 +9,6 @@ export function useAiCallText(options) {
|
|
|
9
9
|
setLoading(true);
|
|
10
10
|
setError(null);
|
|
11
11
|
try {
|
|
12
|
-
// Vérifier si on est en mode dev (API key contient "dev")
|
|
13
|
-
if (options?.apiKeyId?.includes("dev")) {
|
|
14
|
-
// Simulation pour l'environnement dev
|
|
15
|
-
await new Promise((resolve) => setTimeout(resolve, 1000)); // Délai pour simuler l'API
|
|
16
|
-
let simulatedResult = "";
|
|
17
|
-
// Si le prompt contient des mots-clés pour les chips
|
|
18
|
-
if (request.prompt.toLowerCase().includes("tags") ||
|
|
19
|
-
request.prompt.toLowerCase().includes("chip") ||
|
|
20
|
-
request.prompt.toLowerCase().includes("virgule")) {
|
|
21
|
-
simulatedResult =
|
|
22
|
-
"react, typescript, javascript, frontend, backend, api, development, web, mobile, database";
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
// Lorem ipsum pour les autres cas
|
|
26
|
-
simulatedResult =
|
|
27
|
-
"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.";
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
requestId: `dev-${Date.now()}`,
|
|
31
|
-
text: simulatedResult,
|
|
32
|
-
debitTokens: 150,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
// Mode production : appel API normal
|
|
36
12
|
const result = await client.generateText(request);
|
|
37
13
|
return result;
|
|
38
14
|
}
|
|
@@ -44,7 +20,7 @@ export function useAiCallText(options) {
|
|
|
44
20
|
finally {
|
|
45
21
|
setLoading(false);
|
|
46
22
|
}
|
|
47
|
-
}, [client
|
|
23
|
+
}, [client]);
|
|
48
24
|
return {
|
|
49
25
|
generateText,
|
|
50
26
|
loading,
|