@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.
@@ -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,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;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,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 && 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
@@ -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,2CAyLjB"}
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,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";
package/dist/types.d.ts CHANGED
@@ -12,6 +12,7 @@ export interface BaseAiProps {
12
12
  model?: string | null;
13
13
  prompt?: string | null;
14
14
  editMode?: boolean;
15
+ enableModelManagement?: boolean;
15
16
  onValue?: (value: string) => void;
16
17
  onToast?: (toast: ToastType) => void;
17
18
  disabled?: boolean;
@@ -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): 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.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.19"
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: 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;
@@ -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
- 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";
package/src/types.ts CHANGED
@@ -13,6 +13,7 @@ export interface BaseAiProps {
13
13
  model?: string | null;
14
14
  prompt?: string | null;
15
15
  editMode?: boolean;
16
+ enableModelManagement?: boolean;
16
17
  onValue?: (value: string) => void;
17
18
  onToast?: (toast: ToastType) => void;
18
19
  disabled?: boolean;
@@ -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
+ }