@lastbrain/ai-ui-react 1.0.29 → 1.0.31
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/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +4 -2
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +13 -2
- package/dist/components/AiPromptPanel.js +3 -1
- package/dist/components/AiTextarea.d.ts +1 -1
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +2 -2
- package/dist/components/ErrorToast.d.ts +18 -0
- package/dist/components/ErrorToast.d.ts.map +1 -0
- package/dist/components/ErrorToast.js +123 -0
- package/dist/hooks/useModelManagement.d.ts.map +1 -1
- package/dist/hooks/useModelManagement.js +3 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/errorHandler.d.ts +7 -1
- package/dist/utils/errorHandler.d.ts.map +1 -1
- package/dist/utils/errorHandler.js +19 -6
- package/package.json +2 -2
- package/src/components/AiContextButton.tsx +6 -1
- package/src/components/AiImageButton.tsx +16 -1
- package/src/components/AiPromptPanel.tsx +19 -14
- package/src/components/AiTextarea.tsx +4 -0
- package/src/components/ErrorToast.tsx +183 -0
- package/src/hooks/useModelManagement.ts +21 -8
- package/src/index.ts +4 -0
- package/src/types.ts +1 -0
- package/src/utils/errorHandler.ts +61 -35
|
@@ -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;
|
|
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,2CAyftB"}
|
|
@@ -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;
|
|
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,2CAiZpB"}
|
|
@@ -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;
|
|
@@ -104,6 +106,14 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
|
|
|
104
106
|
model: selectedModel,
|
|
105
107
|
prompt: selectedPrompt,
|
|
106
108
|
size: "1024x1024", // Taille par défaut
|
|
109
|
+
storeOutputs,
|
|
110
|
+
artifactTitle,
|
|
111
|
+
});
|
|
112
|
+
console.log("[AiImageButton] Image generation result:", {
|
|
113
|
+
hasUrl: !!result.url,
|
|
114
|
+
urlLength: result.url?.length,
|
|
115
|
+
urlPreview: result.url?.substring(0, 100),
|
|
116
|
+
requestId: result.requestId,
|
|
107
117
|
});
|
|
108
118
|
if (result.url) {
|
|
109
119
|
// Stocker l'image générée
|
|
@@ -114,6 +124,7 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
|
|
|
114
124
|
tokens: result.debitTokens,
|
|
115
125
|
};
|
|
116
126
|
setGeneratedImage(imageData);
|
|
127
|
+
console.log("[AiImageButton] Image data stored:", imageData);
|
|
117
128
|
onImage?.(result.url, {
|
|
118
129
|
requestId: result.requestId,
|
|
119
130
|
tokens: result.debitTokens,
|
|
@@ -133,7 +144,7 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
|
|
|
133
144
|
}
|
|
134
145
|
}
|
|
135
146
|
catch (error) {
|
|
136
|
-
handleAIError(error, onToast);
|
|
147
|
+
handleAIError(error, onToast, showErrorToast);
|
|
137
148
|
}
|
|
138
149
|
finally {
|
|
139
150
|
setIsOpen(false);
|
|
@@ -242,5 +253,5 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
|
|
|
242
253
|
getThemeStyles().actionButton.color;
|
|
243
254
|
}, 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
255
|
...getThemeStyles().metadata,
|
|
245
|
-
}, children: _jsx("div", { className: "flex justify-center", children: _jsxs("span", { children: ["ID: ", generatedImage.requestId.slice(-8)] }) }) })] }))] }));
|
|
256
|
+
}, children: _jsx("div", { className: "flex justify-center", children: _jsxs("span", { children: ["ID: ", generatedImage.requestId.slice(-8)] }) }) })] })), _jsx(ErrorToast, { error: errorData, onComplete: clearError }, errorKey)] }));
|
|
246
257
|
}
|
|
@@ -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 &&
|
|
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
|
|
@@ -3,5 +3,5 @@ import type { BaseAiProps } from "../types";
|
|
|
3
3
|
export interface AiTextareaProps extends Omit<BaseAiProps, "type">, Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "onValue"> {
|
|
4
4
|
uiMode?: "modal" | "drawer";
|
|
5
5
|
}
|
|
6
|
-
export declare function AiTextarea({ baseUrl, apiKeyId, uiMode, context, model, prompt, editMode, onValue, onToast, disabled, className, ...textareaProps }: AiTextareaProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function AiTextarea({ baseUrl, apiKeyId, uiMode, context, model, prompt, editMode, enableModelManagement, onValue, onToast, disabled, className, ...textareaProps }: AiTextareaProps): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
//# sourceMappingURL=AiTextarea.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAQ5C,MAAM,WAAW,eACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAC9D,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAQ5C,MAAM,WAAW,eACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAC9D,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,qBAAqB,EACrB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CA4LjB"}
|
|
@@ -8,7 +8,7 @@ import { AiPromptPanel } from "./AiPromptPanel";
|
|
|
8
8
|
import { UsageToast, useUsageToast } from "./UsageToast";
|
|
9
9
|
import { aiStyles } from "../styles/inline";
|
|
10
10
|
import { handleAIError } from "../utils/errorHandler";
|
|
11
|
-
export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, onValue, onToast, disabled, className, ...textareaProps }) {
|
|
11
|
+
export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, enableModelManagement, onValue, onToast, disabled, className, ...textareaProps }) {
|
|
12
12
|
const [isOpen, setIsOpen] = useState(false);
|
|
13
13
|
const [textareaValue, setTextareaValue] = useState(textareaProps.value?.toString() ||
|
|
14
14
|
textareaProps.defaultValue?.toString() ||
|
|
@@ -117,5 +117,5 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
|
|
|
117
117
|
...(disabled || loading
|
|
118
118
|
? { opacity: 0.5, cursor: "not-allowed" }
|
|
119
119
|
: {}),
|
|
120
|
-
}, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" }) })) : (_jsx(Sparkles, { size: 16 })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [], sourceText: textareaValue || undefined })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey))] }));
|
|
120
|
+
}, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" }) })) : (_jsx(Sparkles, { size: 16 })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [], sourceText: textareaValue || undefined, baseUrl: baseUrl, apiKey: apiKeyId, enableModelManagement: enableModelManagement })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey))] }));
|
|
121
121
|
}
|
|
@@ -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,kDA4IjB;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,
|
|
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 &&
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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";
|
package/dist/types.d.ts
CHANGED
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
|
3
|
+
"version": "1.0.31",
|
|
4
4
|
"description": "Headless React components for LastBrain AI UI Kit",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"lucide-react": "^0.257.0",
|
|
51
|
-
"@lastbrain/ai-ui-core": "1.0.
|
|
51
|
+
"@lastbrain/ai-ui-core": "1.0.20"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/react": "^19.2.0",
|
|
@@ -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,9 @@ 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 key={errorKey} error={errorData} onComplete={clearError} />
|
|
551
556
|
</>
|
|
552
557
|
);
|
|
553
558
|
}
|
|
@@ -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();
|
|
@@ -163,6 +165,15 @@ export function AiImageButton({
|
|
|
163
165
|
model: selectedModel,
|
|
164
166
|
prompt: selectedPrompt,
|
|
165
167
|
size: "1024x1024", // Taille par défaut
|
|
168
|
+
storeOutputs,
|
|
169
|
+
artifactTitle,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
console.log("[AiImageButton] Image generation result:", {
|
|
173
|
+
hasUrl: !!result.url,
|
|
174
|
+
urlLength: result.url?.length,
|
|
175
|
+
urlPreview: result.url?.substring(0, 100),
|
|
176
|
+
requestId: result.requestId,
|
|
166
177
|
});
|
|
167
178
|
|
|
168
179
|
if (result.url) {
|
|
@@ -174,6 +185,7 @@ export function AiImageButton({
|
|
|
174
185
|
tokens: result.debitTokens,
|
|
175
186
|
};
|
|
176
187
|
setGeneratedImage(imageData);
|
|
188
|
+
console.log("[AiImageButton] Image data stored:", imageData);
|
|
177
189
|
|
|
178
190
|
onImage?.(result.url, {
|
|
179
191
|
requestId: result.requestId,
|
|
@@ -194,7 +206,7 @@ export function AiImageButton({
|
|
|
194
206
|
});
|
|
195
207
|
}
|
|
196
208
|
} catch (error) {
|
|
197
|
-
handleAIError(error, onToast);
|
|
209
|
+
handleAIError(error, onToast, showErrorToast);
|
|
198
210
|
} finally {
|
|
199
211
|
setIsOpen(false);
|
|
200
212
|
}
|
|
@@ -438,6 +450,9 @@ export function AiImageButton({
|
|
|
438
450
|
</div>
|
|
439
451
|
</div>
|
|
440
452
|
)}
|
|
453
|
+
|
|
454
|
+
{/* Error Toast */}
|
|
455
|
+
<ErrorToast key={errorKey} error={errorData} onComplete={clearError} />
|
|
441
456
|
</div>
|
|
442
457
|
);
|
|
443
458
|
}
|
|
@@ -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:
|
|
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(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
139
|
-
|
|
143
|
+
? availableModels
|
|
144
|
+
: autoModelManagement.availableModels;
|
|
140
145
|
const effectiveUserModels =
|
|
141
146
|
userModels.length > 0 ? userModels : autoModelManagement.userModels;
|
|
142
147
|
const effectiveToggleModel = onModelToggle || autoModelManagement.toggleModel;
|
|
@@ -30,6 +30,7 @@ export function AiTextarea({
|
|
|
30
30
|
model,
|
|
31
31
|
prompt,
|
|
32
32
|
editMode = false,
|
|
33
|
+
enableModelManagement,
|
|
33
34
|
onValue,
|
|
34
35
|
onToast,
|
|
35
36
|
disabled,
|
|
@@ -208,6 +209,9 @@ export function AiTextarea({
|
|
|
208
209
|
uiMode={uiMode}
|
|
209
210
|
models={models || []}
|
|
210
211
|
sourceText={textareaValue || undefined}
|
|
212
|
+
baseUrl={baseUrl}
|
|
213
|
+
apiKey={apiKeyId}
|
|
214
|
+
enableModelManagement={enableModelManagement}
|
|
211
215
|
/>
|
|
212
216
|
)}
|
|
213
217
|
{Boolean(toastData) && (
|
|
@@ -0,0 +1,183 @@
|
|
|
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
|
|
129
|
+
style={{ fontSize: "12px", opacity: 0.9, wordBreak: "break-word" }}
|
|
130
|
+
>
|
|
131
|
+
{error.message}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<button
|
|
135
|
+
onClick={handleClose}
|
|
136
|
+
style={{
|
|
137
|
+
background: "transparent",
|
|
138
|
+
border: "none",
|
|
139
|
+
cursor: "pointer",
|
|
140
|
+
padding: "4px",
|
|
141
|
+
display: "flex",
|
|
142
|
+
alignItems: "center",
|
|
143
|
+
justifyContent: "center",
|
|
144
|
+
borderRadius: "4px",
|
|
145
|
+
transition: "background-color 150ms ease",
|
|
146
|
+
color: "inherit",
|
|
147
|
+
flexShrink: 0,
|
|
148
|
+
}}
|
|
149
|
+
onMouseEnter={(e) => {
|
|
150
|
+
e.currentTarget.style.backgroundColor = "rgba(239, 68, 68, 0.2)";
|
|
151
|
+
}}
|
|
152
|
+
onMouseLeave={(e) => {
|
|
153
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
154
|
+
}}
|
|
155
|
+
title="Fermer"
|
|
156
|
+
>
|
|
157
|
+
<X size={14} />
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function useErrorToast() {
|
|
164
|
+
const [errorData, setErrorData] = useState<ErrorToastData | null>(null);
|
|
165
|
+
const [errorKey, setErrorKey] = useState(0);
|
|
166
|
+
|
|
167
|
+
const showErrorToast = (error: ErrorToastData) => {
|
|
168
|
+
// Replace any existing error toast with new one
|
|
169
|
+
setErrorKey((prev) => prev + 1);
|
|
170
|
+
setErrorData(error);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const clearError = () => {
|
|
174
|
+
setErrorData(null);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
showErrorToast,
|
|
179
|
+
errorData,
|
|
180
|
+
errorKey,
|
|
181
|
+
clearError,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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(
|
|
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(
|
|
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 (
|
|
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(
|
|
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";
|
package/src/types.ts
CHANGED
|
@@ -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(
|
|
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 (
|
|
66
|
-
|
|
67
|
-
|
|
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 (
|
|
72
|
-
|
|
73
|
-
|
|
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 (
|
|
78
|
-
|
|
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 (
|
|
83
|
-
|
|
91
|
+
if (
|
|
92
|
+
lowercaseMessage.includes("timeout") ||
|
|
93
|
+
lowercaseMessage.includes("timed out")
|
|
94
|
+
) {
|
|
84
95
|
return ErrorCode.TIMEOUT;
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
if (
|
|
88
|
-
|
|
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(
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
+
}
|