@lastbrain/ai-ui-react 1.0.29 → 1.0.30

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.
@@ -1 +1 @@
1
- {"version":3,"file":"AiContextButton.d.ts","sourceRoot":"","sources":["../../src/components/AiContextButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAS5C,MAAM,WAAW,oBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IAEvE,WAAW,EAAE,GAAG,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,WAAW,EACX,kBAAyC,EACzC,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,gBAA0C,EAC1C,YAAY,EACZ,aAAa,EACb,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,GAAG,WAAW,EACf,EAAE,oBAAoB,2CAqftB"}
1
+ {"version":3,"file":"AiContextButton.d.ts","sourceRoot":"","sources":["../../src/components/AiContextButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAU5C,MAAM,WAAW,oBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IAEvE,WAAW,EAAE,GAAG,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,WAAW,EACX,kBAAyC,EACzC,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,gBAA0C,EAC1C,YAAY,EACZ,aAAa,EACb,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,GAAG,WAAW,EACf,EAAE,oBAAoB,2CA6ftB"}
@@ -6,6 +6,7 @@ import { useAiCallText } from "../hooks/useAiCallText";
6
6
  import { useAiModels } from "../hooks/useAiModels";
7
7
  import { AiPromptPanel } from "./AiPromptPanel";
8
8
  import { useUsageToast } from "./UsageToast";
9
+ import { useErrorToast, ErrorToast } from "./ErrorToast";
9
10
  import { aiStyles } from "../styles/inline";
10
11
  import { useAiContext } from "../context/AiProvider";
11
12
  import { handleAIError } from "../utils/errorHandler";
@@ -14,6 +15,7 @@ export function AiContextButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId,
14
15
  const [isResultOpen, setIsResultOpen] = useState(false);
15
16
  const [analysisResult, setAnalysisResult] = useState(null);
16
17
  const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
18
+ const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
17
19
  // Récupérer le contexte AiProvider avec fallback sur les props
18
20
  const aiContext = useAiContext();
19
21
  const baseUrl = propBaseUrl ?? aiContext.baseUrl;
@@ -155,7 +157,7 @@ Analyse ces données et réponds de manière structurée et claire.`;
155
157
  }
156
158
  catch (error) {
157
159
  console.error("AiContextButton error:", error);
158
- handleAIError(error, onToast);
160
+ handleAIError(error, onToast, showErrorToast);
159
161
  }
160
162
  finally {
161
163
  setIsOpen(false);
@@ -333,5 +335,5 @@ Analyse ces données et réponds de manière structurée et claire.`;
333
335
  ...getThemeStyles().content,
334
336
  }, children: [_jsxs("span", { children: ["Co\u00FBt: $", (apiKeyId?.includes("dev")
335
337
  ? 0
336
- : analysisResult.cost).toFixed(6)] }), _jsxs("span", { children: ["ID: ", analysisResult.requestId?.slice(-8) || "N/A"] })] }) })] })] }) }) }))] }));
338
+ : analysisResult.cost).toFixed(6)] }), _jsxs("span", { children: ["ID: ", analysisResult.requestId?.slice(-8) || "N/A"] })] }) })] })] }) }) })), _jsx(ErrorToast, { error: errorData, onComplete: clearError }, errorKey)] }));
337
339
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AiImageButton.d.ts","sourceRoot":"","sources":["../../src/components/AiImageButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAS5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAS5C,MAAM,WAAW,kBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACvE,OAAO,CAAC,EAAE,CACR,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,GAAG,WAAW,EACf,EAAE,kBAAkB,2CAmYpB"}
1
+ {"version":3,"file":"AiImageButton.d.ts","sourceRoot":"","sources":["../../src/components/AiImageButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAS5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAU5C,MAAM,WAAW,kBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACvE,OAAO,CAAC,EAAE,CACR,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,GAAG,WAAW,EACf,EAAE,kBAAkB,2CA2YpB"}
@@ -6,6 +6,7 @@ import { useAiCallImage } from "../hooks/useAiCallImage";
6
6
  import { useAiModels } from "../hooks/useAiModels";
7
7
  import { AiPromptPanel } from "./AiPromptPanel";
8
8
  import { useUsageToast } from "./UsageToast";
9
+ import { useErrorToast, ErrorToast } from "./ErrorToast";
9
10
  import { aiStyles } from "../styles/inline";
10
11
  import { useAiContext } from "../context/AiProvider";
11
12
  import { handleAIError } from "../utils/errorHandler";
@@ -13,6 +14,7 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
13
14
  const [isOpen, setIsOpen] = useState(false);
14
15
  const [generatedImage, setGeneratedImage] = useState(null);
15
16
  const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
17
+ const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
16
18
  // Récupérer le contexte AiProvider avec fallback sur les props
17
19
  const aiContext = useAiContext();
18
20
  const baseUrl = propBaseUrl ?? aiContext.baseUrl;
@@ -133,7 +135,7 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
133
135
  }
134
136
  }
135
137
  catch (error) {
136
- handleAIError(error, onToast);
138
+ handleAIError(error, onToast, showErrorToast);
137
139
  }
138
140
  finally {
139
141
  setIsOpen(false);
@@ -242,5 +244,5 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
242
244
  getThemeStyles().actionButton.color;
243
245
  }, title: "Copier l'URL", children: [_jsx(Copy, { size: 14 }), "Copier URL"] }), onImageSave && (_jsxs("button", { onClick: handleSave, className: "flex items-center gap-1 px-3 py-2 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors", title: "Sauvegarder en base", children: [_jsx(ExternalLink, { size: 14 }), "Sauvegarder"] }))] }), _jsx("div", { className: "mt-3 pt-3 text-xs", style: {
244
246
  ...getThemeStyles().metadata,
245
- }, children: _jsx("div", { className: "flex justify-center", children: _jsxs("span", { children: ["ID: ", generatedImage.requestId.slice(-8)] }) }) })] }))] }));
247
+ }, children: _jsx("div", { className: "flex justify-center", children: _jsxs("span", { children: ["ID: ", generatedImage.requestId.slice(-8)] }) }) })] })), _jsx(ErrorToast, { error: errorData, onComplete: clearError }, errorKey)] }));
246
248
  }
@@ -42,7 +42,9 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
42
42
  apiKey,
43
43
  baseUrl,
44
44
  category: "text", // Par défaut pour AiPromptPanel
45
- autoFetch: enableModelManagement && models.length === 0 && availableModels.length === 0,
45
+ autoFetch: enableModelManagement &&
46
+ models.length === 0 &&
47
+ availableModels.length === 0,
46
48
  });
47
49
  // Utiliser soit les props externes soit la gestion automatique
48
50
  const effectiveAvailableModels = models.length > 0
@@ -0,0 +1,18 @@
1
+ interface ErrorToastData {
2
+ message: string;
3
+ code?: string;
4
+ }
5
+ interface ErrorToastProps {
6
+ error: ErrorToastData | null;
7
+ position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
8
+ onComplete?: () => void;
9
+ }
10
+ export declare function ErrorToast({ error, position, onComplete, }: ErrorToastProps): import("react/jsx-runtime").JSX.Element | null;
11
+ export declare function useErrorToast(): {
12
+ showErrorToast: (error: ErrorToastData) => void;
13
+ errorData: ErrorToastData | null;
14
+ errorKey: number;
15
+ clearError: () => void;
16
+ };
17
+ export {};
18
+ //# sourceMappingURL=ErrorToast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorToast.d.ts","sourceRoot":"","sources":["../../src/components/ErrorToast.tsx"],"names":[],"mappings":"AAKA,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,kDA0IjB;AAED,wBAAgB,aAAa;4BAII,cAAc;;;;EAgB9C"}
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { X, AlertCircle } from "lucide-react";
5
+ export function ErrorToast({ error, position = "bottom-right", onComplete, }) {
6
+ const [isVisible, setIsVisible] = useState(false);
7
+ const [isClosing, setIsClosing] = useState(false);
8
+ const fadeTimeoutRef = useRef(null);
9
+ const autoCloseTimeoutRef = useRef(null);
10
+ useEffect(() => {
11
+ if (error) {
12
+ // Show toast immediately
13
+ queueMicrotask(() => {
14
+ setIsVisible(true);
15
+ setIsClosing(false);
16
+ });
17
+ // Auto-close after 8 seconds for errors (longer than success messages)
18
+ autoCloseTimeoutRef.current = window.setTimeout(() => {
19
+ handleClose();
20
+ }, 8000);
21
+ }
22
+ return () => {
23
+ if (fadeTimeoutRef.current) {
24
+ window.clearTimeout(fadeTimeoutRef.current);
25
+ }
26
+ if (autoCloseTimeoutRef.current) {
27
+ window.clearTimeout(autoCloseTimeoutRef.current);
28
+ }
29
+ };
30
+ }, [error]);
31
+ const handleClose = () => {
32
+ if (isClosing)
33
+ return;
34
+ // Clear auto-close timeout if user closes manually
35
+ if (autoCloseTimeoutRef.current) {
36
+ window.clearTimeout(autoCloseTimeoutRef.current);
37
+ }
38
+ setIsClosing(true);
39
+ fadeTimeoutRef.current = window.setTimeout(() => {
40
+ setIsVisible(false);
41
+ onComplete?.();
42
+ }, 200);
43
+ };
44
+ if (!error)
45
+ return null;
46
+ const getPositionStyles = () => {
47
+ const baseStyles = {
48
+ position: "fixed",
49
+ zIndex: 9999,
50
+ pointerEvents: "auto",
51
+ };
52
+ switch (position) {
53
+ case "bottom-left":
54
+ return { ...baseStyles, bottom: "8px", left: "8px" };
55
+ case "top-right":
56
+ return { ...baseStyles, top: "8px", right: "8px" };
57
+ case "top-left":
58
+ return { ...baseStyles, top: "8px", left: "8px" };
59
+ default:
60
+ return { ...baseStyles, bottom: "8px", right: "8px" };
61
+ }
62
+ };
63
+ return (_jsxs("div", { style: {
64
+ ...getPositionStyles(),
65
+ opacity: isVisible && !isClosing ? 1 : 0,
66
+ transform: `translateY(${isVisible && !isClosing ? "0" : "8px"})`,
67
+ transition: "opacity 200ms ease, transform 200ms ease",
68
+ padding: "12px 16px",
69
+ borderRadius: "8px",
70
+ background: "rgba(239, 68, 68, 0.12)",
71
+ border: "1px solid rgba(239, 68, 68, 0.3)",
72
+ color: "#dc2626",
73
+ fontSize: "13px",
74
+ fontWeight: 500,
75
+ display: "flex",
76
+ alignItems: "flex-start",
77
+ gap: "10px",
78
+ boxShadow: "0 4px 12px -2px rgba(239, 68, 68, 0.2)",
79
+ backdropFilter: "blur(8px)",
80
+ WebkitBackdropFilter: "blur(8px)",
81
+ maxWidth: "400px",
82
+ minWidth: "280px",
83
+ }, 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: ["Erreur", error.code && (_jsxs("span", { style: {
84
+ marginLeft: "8px",
85
+ fontSize: "10px",
86
+ opacity: 0.7,
87
+ fontFamily: "monospace",
88
+ }, children: ["[", error.code, "]"] }))] }), _jsx("div", { style: { fontSize: "12px", opacity: 0.9, wordBreak: "break-word" }, children: error.message })] }), _jsx("button", { onClick: handleClose, style: {
89
+ background: "transparent",
90
+ border: "none",
91
+ cursor: "pointer",
92
+ padding: "4px",
93
+ display: "flex",
94
+ alignItems: "center",
95
+ justifyContent: "center",
96
+ borderRadius: "4px",
97
+ transition: "background-color 150ms ease",
98
+ color: "inherit",
99
+ flexShrink: 0,
100
+ }, onMouseEnter: (e) => {
101
+ e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)";
102
+ }, onMouseLeave: (e) => {
103
+ e.currentTarget.style.backgroundColor = "transparent";
104
+ }, title: "Fermer", children: _jsx(X, { size: 14 }) })] }));
105
+ }
106
+ export function useErrorToast() {
107
+ const [errorData, setErrorData] = useState(null);
108
+ const [errorKey, setErrorKey] = useState(0);
109
+ const showErrorToast = (error) => {
110
+ // Replace any existing error toast with new one
111
+ setErrorKey((prev) => prev + 1);
112
+ setErrorData(error);
113
+ };
114
+ const clearError = () => {
115
+ setErrorData(null);
116
+ };
117
+ return {
118
+ showErrorToast,
119
+ errorData,
120
+ errorKey,
121
+ clearError,
122
+ };
123
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"useModelManagement.d.ts","sourceRoot":"","sources":["../../src/hooks/useModelManagement.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAQlC,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACjD;AAED,MAAM,WAAW,wBAAwB;IAEvC,eAAe,EAAE,OAAO,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAGrB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,eAAe,EAAE,MAAM,OAAO,EAAE,CAAC;IACjC,iBAAiB,EAAE,MAAM,OAAO,EAAE,CAAC;CACpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,wBAAwB,CAyN1B"}
1
+ {"version":3,"file":"useModelManagement.d.ts","sourceRoot":"","sources":["../../src/hooks/useModelManagement.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAQlC,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACjD;AAED,MAAM,WAAW,wBAAwB;IAEvC,eAAe,EAAE,OAAO,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAGrB,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,eAAe,EAAE,MAAM,OAAO,EAAE,CAAC;IACjC,iBAAiB,EAAE,MAAM,OAAO,EAAE,CAAC;CACpC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,yBAA8B,GACtC,wBAAwB,CAsO1B"}
@@ -152,7 +152,9 @@ export function useModelManagement(options = {}) {
152
152
  baseUrl: effectiveOptions.baseUrl || "none",
153
153
  });
154
154
  // Auto-fetch si autoFetch est activé ET qu'on a soit une apiKey soit un proxy externe ET API disponible
155
- if (autoFetch && !apiUnavailable && (effectiveOptions.apiKey || isExternalProxy)) {
155
+ if (autoFetch &&
156
+ !apiUnavailable &&
157
+ (effectiveOptions.apiKey || isExternalProxy)) {
156
158
  console.log("[useModelManagement] Starting auto-fetch...");
157
159
  Promise.all([refreshModels(), refreshUserModels()]);
158
160
  }
package/dist/index.d.ts CHANGED
@@ -17,6 +17,8 @@ export * from "./components/AiImageButton";
17
17
  export * from "./components/AiContextButton";
18
18
  export * from "./components/AiSettingsButton";
19
19
  export * from "./components/AiStatusButton";
20
+ export * from "./components/ErrorToast";
21
+ export * from "./components/UsageToast";
20
22
  export * from "./utils/modelManagement";
21
23
  export * from "./utils/cache";
22
24
  export * from "./examples/AiImageGenerator";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AAGxB,cAAc,sBAAsB,CAAC;AAGrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAG5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAG9B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AAGxB,cAAc,sBAAsB,CAAC;AAGrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAG3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAG5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAG9B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC"}
package/dist/index.js CHANGED
@@ -21,6 +21,9 @@ export * from "./components/AiImageButton";
21
21
  export * from "./components/AiContextButton";
22
22
  export * from "./components/AiSettingsButton";
23
23
  export * from "./components/AiStatusButton";
24
+ // Toast system
25
+ export * from "./components/ErrorToast";
26
+ export * from "./components/UsageToast";
24
27
  // Utils
25
28
  export * from "./utils/modelManagement";
26
29
  export * from "./utils/cache";
@@ -19,6 +19,12 @@ export interface ErrorToastCallback {
19
19
  }
20
20
  /**
21
21
  * Gestionnaire d'erreur uniforme pour tous les composants AI
22
+ * @param error L'erreur à gérer
23
+ * @param onToast Callback externe optionnelle fournie par le parent
24
+ * @param showInternalToast Callback interne optionnelle pour afficher un toast dans le composant
22
25
  */
23
- export declare function handleAIError(error: any, onToast?: ErrorToastCallback): void;
26
+ export declare function handleAIError(error: any, onToast?: ErrorToastCallback, showInternalToast?: (error: {
27
+ message: string;
28
+ code?: string;
29
+ }) => void): void;
24
30
  //# sourceMappingURL=errorHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/utils/errorHandler.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,WAAW,CA2CpD;AA+ED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,KAAK,EAAE;QACN,IAAI,EAAE,OAAO,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;CACV;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAa5E"}
1
+ {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/utils/errorHandler.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,WAAW,CA8CpD;AAyFD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAClE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,GAAG,EACV,OAAO,CAAC,EAAE,kBAAkB,EAC5B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,GACtE,IAAI,CAuBN"}
@@ -107,16 +107,29 @@ function getUserFriendlyMessage(code, originalMessage) {
107
107
  }
108
108
  /**
109
109
  * Gestionnaire d'erreur uniforme pour tous les composants AI
110
+ * @param error L'erreur à gérer
111
+ * @param onToast Callback externe optionnelle fournie par le parent
112
+ * @param showInternalToast Callback interne optionnelle pour afficher un toast dans le composant
110
113
  */
111
- export function handleAIError(error, onToast) {
114
+ export function handleAIError(error, onToast, showInternalToast) {
112
115
  const parsedError = parseAIError(error);
113
116
  console.error("[AI Error]", {
114
117
  original: error,
115
118
  parsed: parsedError,
116
119
  });
117
- onToast?.({
118
- type: "error",
119
- message: parsedError.message,
120
- code: parsedError.code,
121
- });
120
+ // Callback externe (prioritaire)
121
+ if (onToast) {
122
+ onToast({
123
+ type: "error",
124
+ message: parsedError.message,
125
+ code: parsedError.code,
126
+ });
127
+ }
128
+ // Callback interne (fallback si pas de callback externe)
129
+ else if (showInternalToast) {
130
+ showInternalToast({
131
+ message: parsedError.message,
132
+ code: parsedError.code,
133
+ });
134
+ }
122
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/ai-ui-react",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "Headless React components for LastBrain AI UI Kit",
5
5
  "private": false,
6
6
  "type": "module",
@@ -7,6 +7,7 @@ import { useAiCallText } from "../hooks/useAiCallText";
7
7
  import { useAiModels } from "../hooks/useAiModels";
8
8
  import { AiPromptPanel } from "./AiPromptPanel";
9
9
  import { useUsageToast } from "./UsageToast";
10
+ import { useErrorToast, ErrorToast } from "./ErrorToast";
10
11
  import { aiStyles } from "../styles/inline";
11
12
  import { useAiContext } from "../context/AiProvider";
12
13
  import { handleAIError } from "../utils/errorHandler";
@@ -60,6 +61,7 @@ export function AiContextButton({
60
61
  cost: number;
61
62
  } | null>(null);
62
63
  const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
64
+ const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
63
65
 
64
66
  // Récupérer le contexte AiProvider avec fallback sur les props
65
67
  const aiContext = useAiContext();
@@ -223,7 +225,7 @@ Analyse ces données et réponds de manière structurée et claire.`;
223
225
  }
224
226
  } catch (error) {
225
227
  console.error("AiContextButton error:", error);
226
- handleAIError(error, onToast);
228
+ handleAIError(error, onToast, showErrorToast);
227
229
  } finally {
228
230
  setIsOpen(false);
229
231
  }
@@ -548,6 +550,13 @@ Analyse ces données et réponds de manière structurée et claire.`;
548
550
  </div>
549
551
  </div>
550
552
  )}
553
+
554
+ {/* Error Toast */}
555
+ <ErrorToast
556
+ key={errorKey}
557
+ error={errorData}
558
+ onComplete={clearError}
559
+ />
551
560
  </>
552
561
  );
553
562
  }
@@ -14,6 +14,7 @@ import { useAiCallImage } from "../hooks/useAiCallImage";
14
14
  import { useAiModels } from "../hooks/useAiModels";
15
15
  import { AiPromptPanel } from "./AiPromptPanel";
16
16
  import { useUsageToast } from "./UsageToast";
17
+ import { useErrorToast, ErrorToast } from "./ErrorToast";
17
18
  import { aiStyles } from "../styles/inline";
18
19
  import { useAiContext } from "../context/AiProvider";
19
20
  import { handleAIError } from "../utils/errorHandler";
@@ -62,6 +63,7 @@ export function AiImageButton({
62
63
  tokens: number;
63
64
  } | null>(null);
64
65
  const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
66
+ const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
65
67
 
66
68
  // Récupérer le contexte AiProvider avec fallback sur les props
67
69
  const aiContext = useAiContext();
@@ -194,7 +196,7 @@ export function AiImageButton({
194
196
  });
195
197
  }
196
198
  } catch (error) {
197
- handleAIError(error, onToast);
199
+ handleAIError(error, onToast, showErrorToast);
198
200
  } finally {
199
201
  setIsOpen(false);
200
202
  }
@@ -438,6 +440,13 @@ export function AiImageButton({
438
440
  </div>
439
441
  </div>
440
442
  )}
443
+
444
+ {/* Error Toast */}
445
+ <ErrorToast
446
+ key={errorKey}
447
+ error={errorData}
448
+ onComplete={clearError}
449
+ />
441
450
  </div>
442
451
  );
443
452
  }
@@ -57,7 +57,7 @@ export interface AiPromptPanelRenderProps {
57
57
 
58
58
  export function AiPromptPanel(props: AiPromptPanelProps) {
59
59
  const { apiKey, baseUrl } = props;
60
-
60
+
61
61
  // Si apiKey et baseUrl sont fournis, wrapper avec AiProvider
62
62
  if (apiKey || baseUrl) {
63
63
  return (
@@ -66,7 +66,7 @@ export function AiPromptPanel(props: AiPromptPanelProps) {
66
66
  </AiProvider>
67
67
  );
68
68
  }
69
-
69
+
70
70
  // Sinon, utiliser le contexte existant
71
71
  return <AiPromptPanelInternal {...props} />;
72
72
  }
@@ -119,24 +119,29 @@ function AiPromptPanelInternal({
119
119
  apiKey,
120
120
  baseUrl,
121
121
  category: "text", // Par défaut pour AiPromptPanel
122
- autoFetch: enableModelManagement && models.length === 0 && availableModels.length === 0,
122
+ autoFetch:
123
+ enableModelManagement &&
124
+ models.length === 0 &&
125
+ availableModels.length === 0,
123
126
  });
124
127
 
125
128
  // Utiliser soit les props externes soit la gestion automatique
126
129
  const effectiveAvailableModels =
127
130
  models.length > 0
128
- ? models.map((m): AIModel => ({
129
- id: m.id,
130
- name: m.name,
131
- category: m.type === "image" ? "image" : "text",
132
- provider: m.provider,
133
- description: `${m.provider} model`,
134
- isPro: false,
135
- isActive: true,
136
- }))
131
+ ? models.map(
132
+ (m): AIModel => ({
133
+ id: m.id,
134
+ name: m.name,
135
+ category: m.type === "image" ? "image" : "text",
136
+ provider: m.provider,
137
+ description: `${m.provider} model`,
138
+ isPro: false,
139
+ isActive: true,
140
+ })
141
+ )
137
142
  : availableModels.length > 0
138
- ? availableModels
139
- : autoModelManagement.availableModels;
143
+ ? availableModels
144
+ : autoModelManagement.availableModels;
140
145
  const effectiveUserModels =
141
146
  userModels.length > 0 ? userModels : autoModelManagement.userModels;
142
147
  const effectiveToggleModel = onModelToggle || autoModelManagement.toggleModel;
@@ -0,0 +1,181 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { X, AlertCircle } from "lucide-react";
5
+
6
+ interface ErrorToastData {
7
+ message: string;
8
+ code?: string;
9
+ }
10
+
11
+ interface ErrorToastProps {
12
+ error: ErrorToastData | null;
13
+ position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
14
+ onComplete?: () => void;
15
+ }
16
+
17
+ export function ErrorToast({
18
+ error,
19
+ position = "bottom-right",
20
+ onComplete,
21
+ }: ErrorToastProps) {
22
+ const [isVisible, setIsVisible] = useState(false);
23
+ const [isClosing, setIsClosing] = useState(false);
24
+ const fadeTimeoutRef = useRef<number | null>(null);
25
+ const autoCloseTimeoutRef = useRef<number | null>(null);
26
+
27
+ useEffect(() => {
28
+ if (error) {
29
+ // Show toast immediately
30
+ queueMicrotask(() => {
31
+ setIsVisible(true);
32
+ setIsClosing(false);
33
+ });
34
+
35
+ // Auto-close after 8 seconds for errors (longer than success messages)
36
+ autoCloseTimeoutRef.current = window.setTimeout(() => {
37
+ handleClose();
38
+ }, 8000);
39
+ }
40
+
41
+ return () => {
42
+ if (fadeTimeoutRef.current) {
43
+ window.clearTimeout(fadeTimeoutRef.current);
44
+ }
45
+ if (autoCloseTimeoutRef.current) {
46
+ window.clearTimeout(autoCloseTimeoutRef.current);
47
+ }
48
+ };
49
+ }, [error]);
50
+
51
+ const handleClose = () => {
52
+ if (isClosing) return;
53
+
54
+ // Clear auto-close timeout if user closes manually
55
+ if (autoCloseTimeoutRef.current) {
56
+ window.clearTimeout(autoCloseTimeoutRef.current);
57
+ }
58
+
59
+ setIsClosing(true);
60
+ fadeTimeoutRef.current = window.setTimeout(() => {
61
+ setIsVisible(false);
62
+ onComplete?.();
63
+ }, 200);
64
+ };
65
+
66
+ if (!error) return null;
67
+
68
+ const getPositionStyles = () => {
69
+ const baseStyles = {
70
+ position: "fixed" as const,
71
+ zIndex: 9999,
72
+ pointerEvents: "auto" as const,
73
+ };
74
+
75
+ switch (position) {
76
+ case "bottom-left":
77
+ return { ...baseStyles, bottom: "8px", left: "8px" };
78
+ case "top-right":
79
+ return { ...baseStyles, top: "8px", right: "8px" };
80
+ case "top-left":
81
+ return { ...baseStyles, top: "8px", left: "8px" };
82
+ default:
83
+ return { ...baseStyles, bottom: "8px", right: "8px" };
84
+ }
85
+ };
86
+
87
+ return (
88
+ <div
89
+ style={{
90
+ ...getPositionStyles(),
91
+ opacity: isVisible && !isClosing ? 1 : 0,
92
+ transform: `translateY(${isVisible && !isClosing ? "0" : "8px"})`,
93
+ transition: "opacity 200ms ease, transform 200ms ease",
94
+ padding: "12px 16px",
95
+ borderRadius: "8px",
96
+ background: "rgba(239, 68, 68, 0.12)",
97
+ border: "1px solid rgba(239, 68, 68, 0.3)",
98
+ color: "#dc2626",
99
+ fontSize: "13px",
100
+ fontWeight: 500,
101
+ display: "flex",
102
+ alignItems: "flex-start",
103
+ gap: "10px",
104
+ boxShadow: "0 4px 12px -2px rgba(239, 68, 68, 0.2)",
105
+ backdropFilter: "blur(8px)",
106
+ WebkitBackdropFilter: "blur(8px)",
107
+ maxWidth: "400px",
108
+ minWidth: "280px",
109
+ }}
110
+ >
111
+ <AlertCircle size={16} style={{ marginTop: "2px", flexShrink: 0 }} />
112
+ <div style={{ flex: 1, minWidth: 0 }}>
113
+ <div style={{ fontWeight: 600, marginBottom: "2px" }}>
114
+ Erreur
115
+ {error.code && (
116
+ <span
117
+ style={{
118
+ marginLeft: "8px",
119
+ fontSize: "10px",
120
+ opacity: 0.7,
121
+ fontFamily: "monospace",
122
+ }}
123
+ >
124
+ [{error.code}]
125
+ </span>
126
+ )}
127
+ </div>
128
+ <div style={{ fontSize: "12px", opacity: 0.9, wordBreak: "break-word" }}>
129
+ {error.message}
130
+ </div>
131
+ </div>
132
+ <button
133
+ onClick={handleClose}
134
+ style={{
135
+ background: "transparent",
136
+ border: "none",
137
+ cursor: "pointer",
138
+ padding: "4px",
139
+ display: "flex",
140
+ alignItems: "center",
141
+ justifyContent: "center",
142
+ borderRadius: "4px",
143
+ transition: "background-color 150ms ease",
144
+ color: "inherit",
145
+ flexShrink: 0,
146
+ }}
147
+ onMouseEnter={(e) => {
148
+ e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)";
149
+ }}
150
+ onMouseLeave={(e) => {
151
+ e.currentTarget.style.backgroundColor = "transparent";
152
+ }}
153
+ title="Fermer"
154
+ >
155
+ <X size={14} />
156
+ </button>
157
+ </div>
158
+ );
159
+ }
160
+
161
+ export function useErrorToast() {
162
+ const [errorData, setErrorData] = useState<ErrorToastData | null>(null);
163
+ const [errorKey, setErrorKey] = useState(0);
164
+
165
+ const showErrorToast = (error: ErrorToastData) => {
166
+ // Replace any existing error toast with new one
167
+ setErrorKey((prev) => prev + 1);
168
+ setErrorData(error);
169
+ };
170
+
171
+ const clearError = () => {
172
+ setErrorData(null);
173
+ };
174
+
175
+ return {
176
+ showErrorToast,
177
+ errorData,
178
+ errorKey,
179
+ clearError,
180
+ };
181
+ }
@@ -63,10 +63,13 @@ export function useModelManagement(
63
63
  const [apiUnavailable, setApiUnavailable] = useState(false);
64
64
 
65
65
  // Utiliser baseUrl et apiKey du contexte avec memoization
66
- const effectiveOptions = useMemo(() => ({
67
- baseUrl: options.baseUrl || baseUrl,
68
- apiKey: options.apiKey || apiKeyId,
69
- }), [options.baseUrl, options.apiKey, baseUrl, apiKeyId]);
66
+ const effectiveOptions = useMemo(
67
+ () => ({
68
+ baseUrl: options.baseUrl || baseUrl,
69
+ apiKey: options.apiKey || apiKeyId,
70
+ }),
71
+ [options.baseUrl, options.apiKey, baseUrl, apiKeyId]
72
+ );
70
73
 
71
74
  const refreshModels = useCallback(async () => {
72
75
  try {
@@ -136,7 +139,9 @@ export function useModelManagement(
136
139
  const refreshUserModels = useCallback(async () => {
137
140
  // Si l'API est marquée comme indisponible, ne pas faire l'appel
138
141
  if (apiUnavailable) {
139
- console.log("[useModelManagement] Skipping user models fetch - API marked as unavailable");
142
+ console.log(
143
+ "[useModelManagement] Skipping user models fetch - API marked as unavailable"
144
+ );
140
145
  return;
141
146
  }
142
147
 
@@ -166,7 +171,9 @@ export function useModelManagement(
166
171
  if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
167
172
  setUserModels([]);
168
173
  setApiUnavailable(true);
169
- console.warn("[useModelManagement] User models API not available (404), disabling further requests");
174
+ console.warn(
175
+ "[useModelManagement] User models API not available (404), disabling further requests"
176
+ );
170
177
  }
171
178
  } finally {
172
179
  setLoading(false);
@@ -238,7 +245,11 @@ export function useModelManagement(
238
245
  });
239
246
 
240
247
  // Auto-fetch si autoFetch est activé ET qu'on a soit une apiKey soit un proxy externe ET API disponible
241
- if (autoFetch && !apiUnavailable && (effectiveOptions.apiKey || isExternalProxy)) {
248
+ if (
249
+ autoFetch &&
250
+ !apiUnavailable &&
251
+ (effectiveOptions.apiKey || isExternalProxy)
252
+ ) {
242
253
  console.log("[useModelManagement] Starting auto-fetch...");
243
254
  Promise.all([refreshModels(), refreshUserModels()]);
244
255
  } else if (autoFetch && !effectiveOptions.apiKey && !isExternalProxy) {
@@ -246,7 +257,9 @@ export function useModelManagement(
246
257
  "[useModelManagement] autoFetch is enabled but no apiKey or baseUrl provided. Skipping automatic fetch."
247
258
  );
248
259
  } else if (autoFetch && apiUnavailable) {
249
- console.log("[useModelManagement] autoFetch skipped - API marked as unavailable");
260
+ console.log(
261
+ "[useModelManagement] autoFetch skipped - API marked as unavailable"
262
+ );
250
263
  }
251
264
  }, [
252
265
  autoFetch,
package/src/index.ts CHANGED
@@ -25,6 +25,10 @@ export * from "./components/AiContextButton";
25
25
  export * from "./components/AiSettingsButton";
26
26
  export * from "./components/AiStatusButton";
27
27
 
28
+ // Toast system
29
+ export * from "./components/ErrorToast";
30
+ export * from "./components/UsageToast";
31
+
28
32
  // Utils
29
33
  export * from "./utils/modelManagement";
30
34
  export * from "./utils/cache";
@@ -27,7 +27,10 @@ export function parseAIError(error: any): ParsedError {
27
27
  const errorData = JSON.parse(error.message);
28
28
  if (errorData.error?.code) {
29
29
  return {
30
- message: getUserFriendlyMessage(errorData.error.code, errorData.error.message),
30
+ message: getUserFriendlyMessage(
31
+ errorData.error.code,
32
+ errorData.error.message
33
+ ),
31
34
  code: errorData.error.code,
32
35
  isUserFriendly: true,
33
36
  };
@@ -62,30 +65,40 @@ export function parseAIError(error: any): ParsedError {
62
65
  function detectErrorCode(message: string): string | null {
63
66
  const lowercaseMessage = message.toLowerCase();
64
67
 
65
- if (lowercaseMessage.includes("insufficient_tokens") ||
66
- lowercaseMessage.includes("insufficient balance") ||
67
- lowercaseMessage.includes("not enough tokens")) {
68
+ if (
69
+ lowercaseMessage.includes("insufficient_tokens") ||
70
+ lowercaseMessage.includes("insufficient balance") ||
71
+ lowercaseMessage.includes("not enough tokens")
72
+ ) {
68
73
  return ErrorCode.INSUFFICIENT_TOKENS;
69
74
  }
70
75
 
71
- if (lowercaseMessage.includes("unauthorized") ||
72
- lowercaseMessage.includes("invalid api key") ||
73
- lowercaseMessage.includes("authentication failed")) {
76
+ if (
77
+ lowercaseMessage.includes("unauthorized") ||
78
+ lowercaseMessage.includes("invalid api key") ||
79
+ lowercaseMessage.includes("authentication failed")
80
+ ) {
74
81
  return ErrorCode.UNAUTHORIZED;
75
82
  }
76
83
 
77
- if (lowercaseMessage.includes("model disabled") ||
78
- lowercaseMessage.includes("model not available")) {
84
+ if (
85
+ lowercaseMessage.includes("model disabled") ||
86
+ lowercaseMessage.includes("model not available")
87
+ ) {
79
88
  return ErrorCode.MODEL_DISABLED;
80
89
  }
81
90
 
82
- if (lowercaseMessage.includes("timeout") ||
83
- lowercaseMessage.includes("timed out")) {
91
+ if (
92
+ lowercaseMessage.includes("timeout") ||
93
+ lowercaseMessage.includes("timed out")
94
+ ) {
84
95
  return ErrorCode.TIMEOUT;
85
96
  }
86
97
 
87
- if (lowercaseMessage.includes("network error") ||
88
- lowercaseMessage.includes("fetch failed")) {
98
+ if (
99
+ lowercaseMessage.includes("network error") ||
100
+ lowercaseMessage.includes("fetch failed")
101
+ ) {
89
102
  return ErrorCode.NETWORK_ERROR;
90
103
  }
91
104
 
@@ -99,31 +112,31 @@ function getUserFriendlyMessage(code: string, originalMessage: string): string {
99
112
  switch (code) {
100
113
  case ErrorCode.INSUFFICIENT_TOKENS:
101
114
  return "Solde insuffisant. Veuillez recharger votre compte pour continuer.";
102
-
115
+
103
116
  case ErrorCode.UNAUTHORIZED:
104
117
  return "Clé API invalide ou expirée. Vérifiez votre configuration.";
105
-
118
+
106
119
  case ErrorCode.MODEL_DISABLED:
107
120
  return "Ce modèle n'est pas disponible actuellement.";
108
-
121
+
109
122
  case ErrorCode.TIMEOUT:
110
123
  return "La requête a pris trop de temps. Essayez à nouveau.";
111
-
124
+
112
125
  case ErrorCode.NETWORK_ERROR:
113
126
  return "Erreur de connexion. Vérifiez votre connexion internet.";
114
-
127
+
115
128
  case ErrorCode.PROVIDER_DOWN:
116
129
  return "Service temporairement indisponible. Essayez dans quelques instants.";
117
-
130
+
118
131
  case ErrorCode.BAD_REQUEST:
119
132
  return "Paramètres de requête invalides. Vérifiez votre configuration.";
120
-
133
+
121
134
  case ErrorCode.NO_API_KEY:
122
135
  return "Clé API manquante. Vérifiez votre configuration.";
123
-
136
+
124
137
  case ErrorCode.PRICING_UNAVAILABLE:
125
138
  return "Tarification indisponible pour ce modèle.";
126
-
139
+
127
140
  default:
128
141
  // Pour les erreurs inconnues, essayer d'extraire un message utile
129
142
  if (originalMessage && !originalMessage.startsWith("HTTP ")) {
@@ -137,27 +150,40 @@ function getUserFriendlyMessage(code: string, originalMessage: string): string {
137
150
  * Interface pour la callback de gestion d'erreur uniforme
138
151
  */
139
152
  export interface ErrorToastCallback {
140
- (toast: {
141
- type: "error";
142
- message: string;
143
- code?: string;
144
- }): void;
153
+ (toast: { type: "error"; message: string; code?: string }): void;
145
154
  }
146
155
 
147
156
  /**
148
157
  * Gestionnaire d'erreur uniforme pour tous les composants AI
158
+ * @param error L'erreur à gérer
159
+ * @param onToast Callback externe optionnelle fournie par le parent
160
+ * @param showInternalToast Callback interne optionnelle pour afficher un toast dans le composant
149
161
  */
150
- export function handleAIError(error: any, onToast?: ErrorToastCallback): void {
162
+ export function handleAIError(
163
+ error: any,
164
+ onToast?: ErrorToastCallback,
165
+ showInternalToast?: (error: { message: string; code?: string }) => void
166
+ ): void {
151
167
  const parsedError = parseAIError(error);
152
-
168
+
153
169
  console.error("[AI Error]", {
154
170
  original: error,
155
171
  parsed: parsedError,
156
172
  });
157
173
 
158
- onToast?.({
159
- type: "error",
160
- message: parsedError.message,
161
- code: parsedError.code,
162
- });
163
- }
174
+ // Callback externe (prioritaire)
175
+ if (onToast) {
176
+ onToast({
177
+ type: "error",
178
+ message: parsedError.message,
179
+ code: parsedError.code,
180
+ });
181
+ }
182
+ // Callback interne (fallback si pas de callback externe)
183
+ else if (showInternalToast) {
184
+ showInternalToast({
185
+ message: parsedError.message,
186
+ code: parsedError.code,
187
+ });
188
+ }
189
+ }