@lastbrain/ai-ui-react 1.0.27 → 1.0.29

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":"AiChipLabel.d.ts","sourceRoot":"","sources":["../../src/components/AiChipLabel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAQ/D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,OAAmB,EACnB,SAAS,EACT,KAAK,EAAE,WAAW,GACnB,EAAE,gBAAgB,2CAgClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAU,EACV,QAAQ,EACR,WAAoE,EACpE,OAAO,EACP,QAAQ,EACR,eAAuB,EACvB,SAAS,EACT,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,GACvB,EAAE,gBAAgB,2CA2LlB"}
1
+ {"version":3,"file":"AiChipLabel.d.ts","sourceRoot":"","sources":["../../src/components/AiChipLabel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAS/D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,OAAmB,EACnB,SAAS,EACT,KAAK,EAAE,WAAW,GACnB,EAAE,gBAAgB,2CAgClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAU,EACV,QAAQ,EACR,WAAoE,EACpE,OAAO,EACP,QAAQ,EACR,eAAuB,EACvB,SAAS,EACT,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,GACvB,EAAE,gBAAgB,2CA4LlB"}
@@ -7,6 +7,7 @@ import { AiPromptPanel } from "./AiPromptPanel";
7
7
  import { useAiCallText } from "../hooks/useAiCallText";
8
8
  import { useAiModels } from "../hooks/useAiModels";
9
9
  import { useAiContext } from "../context/AiProvider";
10
+ import { handleAIError } from "../utils/errorHandler";
10
11
  export function AiChipLabel({ children, variant = "default", className, style: customStyle, }) {
11
12
  const variantStyles = {
12
13
  default: {},
@@ -96,6 +97,7 @@ Exemple de réponse attendue: javascript, react, frontend, api, development`;
96
97
  }
97
98
  catch (error) {
98
99
  console.error("Erreur lors de la génération des chips:", error);
100
+ handleAIError(error, undefined);
99
101
  setShowPromptPanel(false);
100
102
  }
101
103
  };
@@ -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;AAQ5C,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,2CAshBtB"}
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"}
@@ -8,6 +8,7 @@ import { AiPromptPanel } from "./AiPromptPanel";
8
8
  import { useUsageToast } from "./UsageToast";
9
9
  import { aiStyles } from "../styles/inline";
10
10
  import { useAiContext } from "../context/AiProvider";
11
+ import { handleAIError } from "../utils/errorHandler";
11
12
  export function AiContextButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode = "modal", contextData, contextDescription = "Données à analyser", onResult, onToast, disabled, className, children, resultModalTitle = "Résultat de l'analyse", storeOutputs, artifactTitle, context: _context, model: _model, prompt: _prompt, ...buttonProps }) {
12
13
  const [isOpen, setIsOpen] = useState(false);
13
14
  const [isResultOpen, setIsResultOpen] = useState(false);
@@ -154,40 +155,7 @@ Analyse ces données et réponds de manière structurée et claire.`;
154
155
  }
155
156
  catch (error) {
156
157
  console.error("AiContextButton error:", error);
157
- // Gestion spécifique des erreurs d'API
158
- let errorMessage = "Erreur lors de l'analyse";
159
- let errorCode;
160
- if (error instanceof Error) {
161
- try {
162
- // Tenter de parser l'erreur JSON si c'est une erreur API
163
- const errorData = JSON.parse(error.message);
164
- if (errorData.error?.code === "INSUFFICIENT_TOKENS") {
165
- errorMessage =
166
- "Crédits insuffisants pour cette analyse. Veuillez recharger votre compte.";
167
- errorCode = "INSUFFICIENT_TOKENS";
168
- }
169
- else if (errorData.error?.message) {
170
- errorMessage = errorData.error.message;
171
- errorCode = errorData.error.code;
172
- }
173
- }
174
- catch {
175
- // Si ce n'est pas du JSON, utiliser le message d'erreur direct
176
- if (error.message.includes("INSUFFICIENT_TOKENS")) {
177
- errorMessage =
178
- "Crédits insuffisants pour cette analyse. Veuillez recharger votre compte.";
179
- errorCode = "INSUFFICIENT_TOKENS";
180
- }
181
- else {
182
- errorMessage = error.message;
183
- }
184
- }
185
- }
186
- onToast?.({
187
- type: "error",
188
- message: errorMessage,
189
- code: errorCode,
190
- });
158
+ handleAIError(error, onToast);
191
159
  }
192
160
  finally {
193
161
  setIsOpen(false);
@@ -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;AAQ5C,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,2CAuYpB"}
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"}
@@ -8,6 +8,7 @@ import { AiPromptPanel } from "./AiPromptPanel";
8
8
  import { useUsageToast } from "./UsageToast";
9
9
  import { aiStyles } from "../styles/inline";
10
10
  import { useAiContext } from "../context/AiProvider";
11
+ import { handleAIError } from "../utils/errorHandler";
11
12
  export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode = "modal", context: _context, model: _model, prompt: _prompt, onImage, onToast, disabled, className, children, showImageCard = true, onImageSave, storeOutputs, artifactTitle, ...buttonProps }) {
12
13
  const [isOpen, setIsOpen] = useState(false);
13
14
  const [generatedImage, setGeneratedImage] = useState(null);
@@ -132,11 +133,7 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
132
133
  }
133
134
  }
134
135
  catch (error) {
135
- onToast?.({
136
- type: "error",
137
- message: "Erreur lors de la génération de l'image",
138
- code: error instanceof Error ? error.message : undefined,
139
- });
136
+ handleAIError(error, onToast);
140
137
  }
141
138
  finally {
142
139
  setIsOpen(false);
@@ -1 +1 @@
1
- {"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAO5C,MAAM,WAAW,YACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACxD,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,qBAA6B,EAC7B,GAAG,UAAU,EACd,EAAE,YAAY,2CAyKd"}
1
+ {"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAQ5C,MAAM,WAAW,YACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACxD,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,qBAA6B,EAC7B,GAAG,UAAU,EACd,EAAE,YAAY,2CA2Kd"}
@@ -7,6 +7,7 @@ import { useAiModels } from "../hooks/useAiModels";
7
7
  import { AiPromptPanel } from "./AiPromptPanel";
8
8
  import { UsageToast, useUsageToast } from "./UsageToast";
9
9
  import { aiStyles } from "../styles/inline";
10
+ import { handleAIError } from "../utils/errorHandler";
10
11
  export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, onValue, onToast, disabled, className, enableModelManagement = false, ...inputProps }) {
11
12
  const [isOpen, setIsOpen] = useState(false);
12
13
  const [inputValue, setInputValue] = useState(inputProps.value?.toString() || inputProps.defaultValue?.toString() || "");
@@ -46,7 +47,8 @@ export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, p
46
47
  }
47
48
  }
48
49
  catch (error) {
49
- onToast?.({ type: "error", message: "Failed to generate text" });
50
+ console.error("AiInput error:", error);
51
+ handleAIError(error, onToast);
50
52
  }
51
53
  finally {
52
54
  setIsOpen(false);
@@ -77,7 +79,8 @@ export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, p
77
79
  }
78
80
  }
79
81
  catch (error) {
80
- onToast?.({ type: "error", message: "Failed to generate text" });
82
+ console.error("AiInput handleQuickGenerate error:", error);
83
+ handleAIError(error, onToast);
81
84
  }
82
85
  };
83
86
  const handleInputChange = (e) => {
@@ -1 +1 @@
1
- {"version":3,"file":"AiPromptPanel.d.ts","sourceRoot":"","sources":["../../src/components/AiPromptPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAOvC,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAG/E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,SAAS,CAAC;IAE1D,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,2CActD"}
1
+ {"version":3,"file":"AiPromptPanel.d.ts","sourceRoot":"","sources":["../../src/components/AiPromptPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAQvC,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAG/E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,SAAS,CAAC;IAE1D,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,2CActD"}
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useState, useEffect, useRef, useLayoutEffect, } from "react";
4
4
  import { BookOpen, Search, Sparkles, Star, Tag, Settings } from "lucide-react";
5
5
  import { aiStyles } from "../styles/inline";
6
+ import { handleAIError } from "../utils/errorHandler";
6
7
  import { usePrompts, } from "../hooks/usePrompts";
7
8
  import { useModelManagement } from "../hooks/useModelManagement";
8
9
  import { AiProvider } from "../context/AiProvider";
@@ -36,19 +37,27 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
36
37
  const [isModelManagementOpen, setIsModelManagementOpen] = useState(false);
37
38
  const [loadingModels, setLoadingModels] = useState([]);
38
39
  const { prompts, loading: promptsLoading, fetchPrompts, incrementStat, } = usePrompts();
39
- // Hook de gestion des modèles (automatique si enableModelManagement et pas de props externes)
40
+ // Hook de gestion des modèles (automatique si enableModelManagement et pas de models/availableModels externes)
40
41
  const autoModelManagement = useModelManagement({
41
42
  apiKey,
42
43
  baseUrl,
43
44
  category: "text", // Par défaut pour AiPromptPanel
44
- autoFetch: enableModelManagement &&
45
- availableModels.length === 0 &&
46
- (!!apiKey || !!baseUrl),
45
+ autoFetch: enableModelManagement && models.length === 0 && availableModels.length === 0,
47
46
  });
48
47
  // Utiliser soit les props externes soit la gestion automatique
49
- const effectiveAvailableModels = availableModels.length > 0
50
- ? availableModels
51
- : autoModelManagement.availableModels;
48
+ const effectiveAvailableModels = models.length > 0
49
+ ? models.map((m) => ({
50
+ id: m.id,
51
+ name: m.name,
52
+ category: m.type === "image" ? "image" : "text",
53
+ provider: m.provider,
54
+ description: `${m.provider} model`,
55
+ isPro: false,
56
+ isActive: true,
57
+ }))
58
+ : availableModels.length > 0
59
+ ? availableModels
60
+ : autoModelManagement.availableModels;
52
61
  const effectiveUserModels = userModels.length > 0 ? userModels : autoModelManagement.userModels;
53
62
  const effectiveToggleModel = onModelToggle || autoModelManagement.toggleModel;
54
63
  // Gestion des modèles
@@ -61,6 +70,7 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
61
70
  }
62
71
  catch (error) {
63
72
  console.error("Erreur lors du changement de modèle:", error);
73
+ handleAIError(error, undefined);
64
74
  }
65
75
  finally {
66
76
  setLoadingModels((prev) => prev.filter((id) => id !== modelId));
@@ -1 +1 @@
1
- {"version":3,"file":"AiSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAO5C,MAAM,WAAW,aACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC1D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,aAAa,2CA+Ef"}
1
+ {"version":3,"file":"AiSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAQ5C,MAAM,WAAW,aACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,CAAC;IAC1D,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,aAAa,2CA+Ef"}
@@ -6,6 +6,7 @@ import { useAiModels } from "../hooks/useAiModels";
6
6
  import { AiPromptPanel } from "./AiPromptPanel";
7
7
  import { UsageToast, useUsageToast } from "./UsageToast";
8
8
  import { aiStyles } from "../styles/inline";
9
+ import { handleAIError } from "../utils/errorHandler";
9
10
  export function AiSelect({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, onValue, onToast, disabled, className, children, ...selectProps }) {
10
11
  const [isOpen, setIsOpen] = useState(false);
11
12
  const [isFocused, setIsFocused] = useState(false);
@@ -33,7 +34,7 @@ export function AiSelect({ baseUrl, apiKeyId, uiMode = "modal", context, model,
33
34
  }
34
35
  }
35
36
  catch (error) {
36
- onToast?.({ type: "error", message: "Failed to generate suggestion" });
37
+ handleAIError(error, onToast);
37
38
  }
38
39
  finally {
39
40
  setIsOpen(false);
@@ -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;AAO5C,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,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CAyLjB"}
@@ -7,6 +7,7 @@ import { useAiModels } from "../hooks/useAiModels";
7
7
  import { AiPromptPanel } from "./AiPromptPanel";
8
8
  import { UsageToast, useUsageToast } from "./UsageToast";
9
9
  import { aiStyles } from "../styles/inline";
10
+ import { handleAIError } from "../utils/errorHandler";
10
11
  export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, onValue, onToast, disabled, className, ...textareaProps }) {
11
12
  const [isOpen, setIsOpen] = useState(false);
12
13
  const [textareaValue, setTextareaValue] = useState(textareaProps.value?.toString() ||
@@ -81,7 +82,7 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
81
82
  }
82
83
  }
83
84
  catch (error) {
84
- onToast?.({ type: "error", message: "Failed to generate text" });
85
+ handleAIError(error, onToast);
85
86
  }
86
87
  };
87
88
  const handleTextareaChange = (e) => {
@@ -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,CA6M1B"}
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,4 +1,4 @@
1
- import { useState, useCallback, useEffect } from "react";
1
+ import { useState, useCallback, useEffect, useMemo } from "react";
2
2
  import { useAiContext } from "../context/AiProvider";
3
3
  import { toggleUserModel, getAvailableModels, getUserModels, } from "../utils/modelManagement";
4
4
  import { getCached, setCache, isRateLimited, getRateLimitResetTime, } from "../utils/cache";
@@ -12,12 +12,12 @@ export function useModelManagement(options = {}) {
12
12
  const [userModels, setUserModels] = useState([]);
13
13
  const [loading, setLoading] = useState(false);
14
14
  const [error, setError] = useState(null);
15
- // Utiliser baseUrl et apiKey du contexte
16
- const effectiveOptions = {
17
- ...apiOptions,
18
- baseUrl: apiOptions.baseUrl || baseUrl,
19
- apiKey: apiOptions.apiKey || apiKeyId,
20
- };
15
+ const [apiUnavailable, setApiUnavailable] = useState(false);
16
+ // Utiliser baseUrl et apiKey du contexte avec memoization
17
+ const effectiveOptions = useMemo(() => ({
18
+ baseUrl: options.baseUrl || baseUrl,
19
+ apiKey: options.apiKey || apiKeyId,
20
+ }), [options.baseUrl, options.apiKey, baseUrl, apiKeyId]);
21
21
  const refreshModels = useCallback(async () => {
22
22
  try {
23
23
  setLoading(true);
@@ -67,6 +67,11 @@ export function useModelManagement(options = {}) {
67
67
  }
68
68
  }, [category, effectiveOptions]);
69
69
  const refreshUserModels = useCallback(async () => {
70
+ // Si l'API est marquée comme indisponible, ne pas faire l'appel
71
+ if (apiUnavailable) {
72
+ console.log("[useModelManagement] Skipping user models fetch - API marked as unavailable");
73
+ return;
74
+ }
70
75
  try {
71
76
  setLoading(true);
72
77
  setError(null);
@@ -84,15 +89,17 @@ export function useModelManagement(options = {}) {
84
89
  const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
85
90
  console.error("[useModelManagement] Error fetching user models:", errorMessage);
86
91
  setError(errorMessage);
87
- // En cas d'erreur 404 (API non disponible), utiliser un array vide
92
+ // En cas d'erreur 404 (API non disponible), utiliser un array vide et ne pas retry
88
93
  if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
89
94
  setUserModels([]);
95
+ setApiUnavailable(true);
96
+ console.warn("[useModelManagement] User models API not available (404), disabling further requests");
90
97
  }
91
98
  }
92
99
  finally {
93
100
  setLoading(false);
94
101
  }
95
- }, [effectiveOptions]);
102
+ }, [effectiveOptions, apiUnavailable]);
96
103
  const toggleModel = useCallback(async (modelId, isActive) => {
97
104
  const currentlyActive = userModels.includes(modelId);
98
105
  const targetState = isActive !== undefined ? isActive : !currentlyActive;
@@ -129,7 +136,7 @@ export function useModelManagement(options = {}) {
129
136
  const getInactiveModels = useCallback(() => {
130
137
  return availableModels.filter((model) => !userModels.includes(model.id));
131
138
  }, [availableModels, userModels]);
132
- // Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe)
139
+ // Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe) ET API disponible
133
140
  useEffect(() => {
134
141
  const isExternalProxy = effectiveOptions.baseUrl &&
135
142
  effectiveOptions.baseUrl.includes("/api/lastbrain");
@@ -138,25 +145,30 @@ export function useModelManagement(options = {}) {
138
145
  hasApiKey: !!effectiveOptions.apiKey,
139
146
  hasBaseUrl: !!effectiveOptions.baseUrl,
140
147
  isExternalProxy,
148
+ apiUnavailable,
141
149
  apiKeyPreview: effectiveOptions.apiKey
142
150
  ? effectiveOptions.apiKey.substring(0, 10) + "..."
143
151
  : "none",
144
152
  baseUrl: effectiveOptions.baseUrl || "none",
145
153
  });
146
- // Auto-fetch si autoFetch est activé ET qu'on a soit une apiKey soit un proxy externe
147
- if (autoFetch && (effectiveOptions.apiKey || isExternalProxy)) {
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)) {
148
156
  console.log("[useModelManagement] Starting auto-fetch...");
149
157
  Promise.all([refreshModels(), refreshUserModels()]);
150
158
  }
151
159
  else if (autoFetch && !effectiveOptions.apiKey && !isExternalProxy) {
152
160
  console.warn("[useModelManagement] autoFetch is enabled but no apiKey or baseUrl provided. Skipping automatic fetch.");
153
161
  }
162
+ else if (autoFetch && apiUnavailable) {
163
+ console.log("[useModelManagement] autoFetch skipped - API marked as unavailable");
164
+ }
154
165
  }, [
155
166
  autoFetch,
156
167
  refreshModels,
157
168
  refreshUserModels,
158
169
  effectiveOptions.apiKey,
159
170
  effectiveOptions.baseUrl,
171
+ apiUnavailable,
160
172
  ]);
161
173
  return {
162
174
  availableModels,
@@ -0,0 +1,24 @@
1
+ export interface ParsedError {
2
+ message: string;
3
+ code?: string;
4
+ isUserFriendly: boolean;
5
+ }
6
+ /**
7
+ * Parse et uniformise la gestion des erreurs des composants AI
8
+ */
9
+ export declare function parseAIError(error: any): ParsedError;
10
+ /**
11
+ * Interface pour la callback de gestion d'erreur uniforme
12
+ */
13
+ export interface ErrorToastCallback {
14
+ (toast: {
15
+ type: "error";
16
+ message: string;
17
+ code?: string;
18
+ }): void;
19
+ }
20
+ /**
21
+ * Gestionnaire d'erreur uniforme pour tous les composants AI
22
+ */
23
+ export declare function handleAIError(error: any, onToast?: ErrorToastCallback): void;
24
+ //# sourceMappingURL=errorHandler.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ import { ErrorCode } from "@lastbrain/ai-ui-core";
3
+ /**
4
+ * Parse et uniformise la gestion des erreurs des composants AI
5
+ */
6
+ export function parseAIError(error) {
7
+ // Si l'erreur est déjà un objet normalisé
8
+ if (error?.code && error?.message) {
9
+ return {
10
+ message: getUserFriendlyMessage(error.code, error.message),
11
+ code: error.code,
12
+ isUserFriendly: true,
13
+ };
14
+ }
15
+ // Tenter de parser une erreur JSON (cas des API publiques)
16
+ if (error instanceof Error) {
17
+ try {
18
+ const errorData = JSON.parse(error.message);
19
+ if (errorData.error?.code) {
20
+ return {
21
+ message: getUserFriendlyMessage(errorData.error.code, errorData.error.message),
22
+ code: errorData.error.code,
23
+ isUserFriendly: true,
24
+ };
25
+ }
26
+ }
27
+ catch {
28
+ // Si le parsing JSON échoue, continuer avec le message d'erreur brut
29
+ }
30
+ // Vérifier les codes d'erreur dans le message
31
+ const errorMessage = error.message || "";
32
+ const detectedCode = detectErrorCode(errorMessage);
33
+ if (detectedCode) {
34
+ return {
35
+ message: getUserFriendlyMessage(detectedCode, errorMessage),
36
+ code: detectedCode,
37
+ isUserFriendly: true,
38
+ };
39
+ }
40
+ }
41
+ // Erreur générique
42
+ return {
43
+ message: "Une erreur est survenue lors de l'appel à l'API IA",
44
+ code: ErrorCode.UNKNOWN,
45
+ isUserFriendly: true,
46
+ };
47
+ }
48
+ /**
49
+ * Détecte le code d'erreur dans un message
50
+ */
51
+ function detectErrorCode(message) {
52
+ const lowercaseMessage = message.toLowerCase();
53
+ if (lowercaseMessage.includes("insufficient_tokens") ||
54
+ lowercaseMessage.includes("insufficient balance") ||
55
+ lowercaseMessage.includes("not enough tokens")) {
56
+ return ErrorCode.INSUFFICIENT_TOKENS;
57
+ }
58
+ if (lowercaseMessage.includes("unauthorized") ||
59
+ lowercaseMessage.includes("invalid api key") ||
60
+ lowercaseMessage.includes("authentication failed")) {
61
+ return ErrorCode.UNAUTHORIZED;
62
+ }
63
+ if (lowercaseMessage.includes("model disabled") ||
64
+ lowercaseMessage.includes("model not available")) {
65
+ return ErrorCode.MODEL_DISABLED;
66
+ }
67
+ if (lowercaseMessage.includes("timeout") ||
68
+ lowercaseMessage.includes("timed out")) {
69
+ return ErrorCode.TIMEOUT;
70
+ }
71
+ if (lowercaseMessage.includes("network error") ||
72
+ lowercaseMessage.includes("fetch failed")) {
73
+ return ErrorCode.NETWORK_ERROR;
74
+ }
75
+ return null;
76
+ }
77
+ /**
78
+ * Génère un message d'erreur convivial selon le code d'erreur
79
+ */
80
+ function getUserFriendlyMessage(code, originalMessage) {
81
+ switch (code) {
82
+ case ErrorCode.INSUFFICIENT_TOKENS:
83
+ return "Solde insuffisant. Veuillez recharger votre compte pour continuer.";
84
+ case ErrorCode.UNAUTHORIZED:
85
+ return "Clé API invalide ou expirée. Vérifiez votre configuration.";
86
+ case ErrorCode.MODEL_DISABLED:
87
+ return "Ce modèle n'est pas disponible actuellement.";
88
+ case ErrorCode.TIMEOUT:
89
+ return "La requête a pris trop de temps. Essayez à nouveau.";
90
+ case ErrorCode.NETWORK_ERROR:
91
+ return "Erreur de connexion. Vérifiez votre connexion internet.";
92
+ case ErrorCode.PROVIDER_DOWN:
93
+ return "Service temporairement indisponible. Essayez dans quelques instants.";
94
+ case ErrorCode.BAD_REQUEST:
95
+ return "Paramètres de requête invalides. Vérifiez votre configuration.";
96
+ case ErrorCode.NO_API_KEY:
97
+ return "Clé API manquante. Vérifiez votre configuration.";
98
+ case ErrorCode.PRICING_UNAVAILABLE:
99
+ return "Tarification indisponible pour ce modèle.";
100
+ default:
101
+ // Pour les erreurs inconnues, essayer d'extraire un message utile
102
+ if (originalMessage && !originalMessage.startsWith("HTTP ")) {
103
+ return originalMessage;
104
+ }
105
+ return "Une erreur inattendue s'est produite. Essayez à nouveau.";
106
+ }
107
+ }
108
+ /**
109
+ * Gestionnaire d'erreur uniforme pour tous les composants AI
110
+ */
111
+ export function handleAIError(error, onToast) {
112
+ const parsedError = parseAIError(error);
113
+ console.error("[AI Error]", {
114
+ original: error,
115
+ parsed: parsedError,
116
+ });
117
+ onToast?.({
118
+ type: "error",
119
+ message: parsedError.message,
120
+ code: parsedError.code,
121
+ });
122
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/ai-ui-react",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
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.18"
51
+ "@lastbrain/ai-ui-core": "1.0.19"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/react": "^19.2.0",
@@ -7,6 +7,7 @@ import { AiPromptPanel } from "./AiPromptPanel";
7
7
  import { useAiCallText } from "../hooks/useAiCallText";
8
8
  import { useAiModels } from "../hooks/useAiModels";
9
9
  import { useAiContext } from "../context/AiProvider";
10
+ import { handleAIError } from "../utils/errorHandler";
10
11
 
11
12
  export interface AiChipLabelProps {
12
13
  children: React.ReactNode;
@@ -150,6 +151,7 @@ Exemple de réponse attendue: javascript, react, frontend, api, development`;
150
151
  addGeneratedChips(chips);
151
152
  } catch (error) {
152
153
  console.error("Erreur lors de la génération des chips:", error);
154
+ handleAIError(error, undefined);
153
155
  setShowPromptPanel(false);
154
156
  }
155
157
  };
@@ -9,6 +9,7 @@ import { AiPromptPanel } from "./AiPromptPanel";
9
9
  import { useUsageToast } from "./UsageToast";
10
10
  import { aiStyles } from "../styles/inline";
11
11
  import { useAiContext } from "../context/AiProvider";
12
+ import { handleAIError } from "../utils/errorHandler";
12
13
 
13
14
  export interface AiContextButtonProps
14
15
  extends
@@ -222,40 +223,7 @@ Analyse ces données et réponds de manière structurée et claire.`;
222
223
  }
223
224
  } catch (error) {
224
225
  console.error("AiContextButton error:", error);
225
-
226
- // Gestion spécifique des erreurs d'API
227
- let errorMessage = "Erreur lors de l'analyse";
228
- let errorCode: string | undefined;
229
-
230
- if (error instanceof Error) {
231
- try {
232
- // Tenter de parser l'erreur JSON si c'est une erreur API
233
- const errorData = JSON.parse(error.message);
234
- if (errorData.error?.code === "INSUFFICIENT_TOKENS") {
235
- errorMessage =
236
- "Crédits insuffisants pour cette analyse. Veuillez recharger votre compte.";
237
- errorCode = "INSUFFICIENT_TOKENS";
238
- } else if (errorData.error?.message) {
239
- errorMessage = errorData.error.message;
240
- errorCode = errorData.error.code;
241
- }
242
- } catch {
243
- // Si ce n'est pas du JSON, utiliser le message d'erreur direct
244
- if (error.message.includes("INSUFFICIENT_TOKENS")) {
245
- errorMessage =
246
- "Crédits insuffisants pour cette analyse. Veuillez recharger votre compte.";
247
- errorCode = "INSUFFICIENT_TOKENS";
248
- } else {
249
- errorMessage = error.message;
250
- }
251
- }
252
- }
253
-
254
- onToast?.({
255
- type: "error",
256
- message: errorMessage,
257
- code: errorCode,
258
- });
226
+ handleAIError(error, onToast);
259
227
  } finally {
260
228
  setIsOpen(false);
261
229
  }
@@ -16,6 +16,7 @@ import { AiPromptPanel } from "./AiPromptPanel";
16
16
  import { useUsageToast } from "./UsageToast";
17
17
  import { aiStyles } from "../styles/inline";
18
18
  import { useAiContext } from "../context/AiProvider";
19
+ import { handleAIError } from "../utils/errorHandler";
19
20
 
20
21
  export interface AiImageButtonProps
21
22
  extends
@@ -193,11 +194,7 @@ export function AiImageButton({
193
194
  });
194
195
  }
195
196
  } catch (error) {
196
- onToast?.({
197
- type: "error",
198
- message: "Erreur lors de la génération de l'image",
199
- code: error instanceof Error ? error.message : undefined,
200
- });
197
+ handleAIError(error, onToast);
201
198
  } finally {
202
199
  setIsOpen(false);
203
200
  }
@@ -8,6 +8,7 @@ import { useAiModels } from "../hooks/useAiModels";
8
8
  import { AiPromptPanel } from "./AiPromptPanel";
9
9
  import { UsageToast, useUsageToast } from "./UsageToast";
10
10
  import { aiStyles } from "../styles/inline";
11
+ import { handleAIError } from "../utils/errorHandler";
11
12
 
12
13
  export interface AiInputProps
13
14
  extends
@@ -83,7 +84,8 @@ export function AiInput({
83
84
  onToast?.({ type: "success", message: "AI generation successful" });
84
85
  }
85
86
  } catch (error) {
86
- onToast?.({ type: "error", message: "Failed to generate text" });
87
+ console.error("AiInput error:", error);
88
+ handleAIError(error, onToast);
87
89
  } finally {
88
90
  setIsOpen(false);
89
91
  }
@@ -116,7 +118,8 @@ export function AiInput({
116
118
  onToast?.({ type: "success", message: "AI generation successful" });
117
119
  }
118
120
  } catch (error) {
119
- onToast?.({ type: "error", message: "Failed to generate text" });
121
+ console.error("AiInput handleQuickGenerate error:", error);
122
+ handleAIError(error, onToast);
120
123
  }
121
124
  };
122
125
 
@@ -11,6 +11,7 @@ import { BookOpen, Search, Sparkles, Star, Tag, Settings } from "lucide-react";
11
11
  import type { ModelRef } from "@lastbrain/ai-ui-core";
12
12
  import type { UiMode } from "../types";
13
13
  import { aiStyles } from "../styles/inline";
14
+ import { handleAIError } from "../utils/errorHandler";
14
15
  import {
15
16
  usePrompts,
16
17
  type Prompt,
@@ -113,20 +114,27 @@ function AiPromptPanelInternal({
113
114
  incrementStat,
114
115
  } = usePrompts();
115
116
 
116
- // Hook de gestion des modèles (automatique si enableModelManagement et pas de props externes)
117
+ // Hook de gestion des modèles (automatique si enableModelManagement et pas de models/availableModels externes)
117
118
  const autoModelManagement = useModelManagement({
118
119
  apiKey,
119
120
  baseUrl,
120
121
  category: "text", // Par défaut pour AiPromptPanel
121
- autoFetch:
122
- enableModelManagement &&
123
- availableModels.length === 0 &&
124
- (!!apiKey || !!baseUrl),
122
+ autoFetch: enableModelManagement && models.length === 0 && availableModels.length === 0,
125
123
  });
126
124
 
127
125
  // Utiliser soit les props externes soit la gestion automatique
128
126
  const effectiveAvailableModels =
129
- availableModels.length > 0
127
+ 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
+ }))
137
+ : availableModels.length > 0
130
138
  ? availableModels
131
139
  : autoModelManagement.availableModels;
132
140
  const effectiveUserModels =
@@ -142,6 +150,7 @@ function AiPromptPanelInternal({
142
150
  await effectiveToggleModel(modelId, isActive);
143
151
  } catch (error) {
144
152
  console.error("Erreur lors du changement de modèle:", error);
153
+ handleAIError(error, undefined);
145
154
  } finally {
146
155
  setLoadingModels((prev) => prev.filter((id) => id !== modelId));
147
156
  }
@@ -7,6 +7,7 @@ import { useAiModels } from "../hooks/useAiModels";
7
7
  import { AiPromptPanel } from "./AiPromptPanel";
8
8
  import { UsageToast, useUsageToast } from "./UsageToast";
9
9
  import { aiStyles } from "../styles/inline";
10
+ import { handleAIError } from "../utils/errorHandler";
10
11
 
11
12
  export interface AiSelectProps
12
13
  extends
@@ -63,7 +64,7 @@ export function AiSelect({
63
64
  showUsageToast(result);
64
65
  }
65
66
  } catch (error) {
66
- onToast?.({ type: "error", message: "Failed to generate suggestion" });
67
+ handleAIError(error, onToast);
67
68
  } finally {
68
69
  setIsOpen(false);
69
70
  }
@@ -13,6 +13,7 @@ import { useAiModels } from "../hooks/useAiModels";
13
13
  import { AiPromptPanel } from "./AiPromptPanel";
14
14
  import { UsageToast, useUsageToast } from "./UsageToast";
15
15
  import { aiStyles } from "../styles/inline";
16
+ import { handleAIError } from "../utils/errorHandler";
16
17
 
17
18
  export interface AiTextareaProps
18
19
  extends
@@ -123,7 +124,7 @@ export function AiTextarea({
123
124
  showUsageToast(result);
124
125
  }
125
126
  } catch (error) {
126
- onToast?.({ type: "error", message: "Failed to generate text" });
127
+ handleAIError(error, onToast);
127
128
  }
128
129
  };
129
130
 
@@ -1,4 +1,4 @@
1
- import { useState, useCallback, useEffect } from "react";
1
+ import { useState, useCallback, useEffect, useMemo } from "react";
2
2
  import { useAiContext } from "../context/AiProvider";
3
3
  import {
4
4
  toggleUserModel,
@@ -60,13 +60,13 @@ export function useModelManagement(
60
60
  const [userModels, setUserModels] = useState<string[]>([]);
61
61
  const [loading, setLoading] = useState(false);
62
62
  const [error, setError] = useState<string | null>(null);
63
+ const [apiUnavailable, setApiUnavailable] = useState(false);
63
64
 
64
- // Utiliser baseUrl et apiKey du contexte
65
- const effectiveOptions = {
66
- ...apiOptions,
67
- baseUrl: apiOptions.baseUrl || baseUrl,
68
- apiKey: apiOptions.apiKey || apiKeyId,
69
- };
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]);
70
70
 
71
71
  const refreshModels = useCallback(async () => {
72
72
  try {
@@ -134,6 +134,12 @@ export function useModelManagement(
134
134
  }, [category, effectiveOptions]);
135
135
 
136
136
  const refreshUserModels = useCallback(async () => {
137
+ // Si l'API est marquée comme indisponible, ne pas faire l'appel
138
+ if (apiUnavailable) {
139
+ console.log("[useModelManagement] Skipping user models fetch - API marked as unavailable");
140
+ return;
141
+ }
142
+
137
143
  try {
138
144
  setLoading(true);
139
145
  setError(null);
@@ -156,14 +162,16 @@ export function useModelManagement(
156
162
  errorMessage
157
163
  );
158
164
  setError(errorMessage);
159
- // En cas d'erreur 404 (API non disponible), utiliser un array vide
165
+ // En cas d'erreur 404 (API non disponible), utiliser un array vide et ne pas retry
160
166
  if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
161
167
  setUserModels([]);
168
+ setApiUnavailable(true);
169
+ console.warn("[useModelManagement] User models API not available (404), disabling further requests");
162
170
  }
163
171
  } finally {
164
172
  setLoading(false);
165
173
  }
166
- }, [effectiveOptions]);
174
+ }, [effectiveOptions, apiUnavailable]);
167
175
 
168
176
  const toggleModel = useCallback(
169
177
  async (modelId: string, isActive?: boolean) => {
@@ -211,7 +219,7 @@ export function useModelManagement(
211
219
  return availableModels.filter((model) => !userModels.includes(model.id));
212
220
  }, [availableModels, userModels]);
213
221
 
214
- // Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe)
222
+ // Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe) ET API disponible
215
223
  useEffect(() => {
216
224
  const isExternalProxy =
217
225
  effectiveOptions.baseUrl &&
@@ -222,20 +230,23 @@ export function useModelManagement(
222
230
  hasApiKey: !!effectiveOptions.apiKey,
223
231
  hasBaseUrl: !!effectiveOptions.baseUrl,
224
232
  isExternalProxy,
233
+ apiUnavailable,
225
234
  apiKeyPreview: effectiveOptions.apiKey
226
235
  ? effectiveOptions.apiKey.substring(0, 10) + "..."
227
236
  : "none",
228
237
  baseUrl: effectiveOptions.baseUrl || "none",
229
238
  });
230
239
 
231
- // Auto-fetch si autoFetch est activé ET qu'on a soit une apiKey soit un proxy externe
232
- if (autoFetch && (effectiveOptions.apiKey || isExternalProxy)) {
240
+ // 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)) {
233
242
  console.log("[useModelManagement] Starting auto-fetch...");
234
243
  Promise.all([refreshModels(), refreshUserModels()]);
235
244
  } else if (autoFetch && !effectiveOptions.apiKey && !isExternalProxy) {
236
245
  console.warn(
237
246
  "[useModelManagement] autoFetch is enabled but no apiKey or baseUrl provided. Skipping automatic fetch."
238
247
  );
248
+ } else if (autoFetch && apiUnavailable) {
249
+ console.log("[useModelManagement] autoFetch skipped - API marked as unavailable");
239
250
  }
240
251
  }, [
241
252
  autoFetch,
@@ -243,6 +254,7 @@ export function useModelManagement(
243
254
  refreshUserModels,
244
255
  effectiveOptions.apiKey,
245
256
  effectiveOptions.baseUrl,
257
+ apiUnavailable,
246
258
  ]);
247
259
 
248
260
  return {
@@ -0,0 +1,163 @@
1
+ "use client";
2
+
3
+ import { ErrorCode } from "@lastbrain/ai-ui-core";
4
+
5
+ export interface ParsedError {
6
+ message: string;
7
+ code?: string;
8
+ isUserFriendly: boolean;
9
+ }
10
+
11
+ /**
12
+ * Parse et uniformise la gestion des erreurs des composants AI
13
+ */
14
+ export function parseAIError(error: any): ParsedError {
15
+ // Si l'erreur est déjà un objet normalisé
16
+ if (error?.code && error?.message) {
17
+ return {
18
+ message: getUserFriendlyMessage(error.code, error.message),
19
+ code: error.code,
20
+ isUserFriendly: true,
21
+ };
22
+ }
23
+
24
+ // Tenter de parser une erreur JSON (cas des API publiques)
25
+ if (error instanceof Error) {
26
+ try {
27
+ const errorData = JSON.parse(error.message);
28
+ if (errorData.error?.code) {
29
+ return {
30
+ message: getUserFriendlyMessage(errorData.error.code, errorData.error.message),
31
+ code: errorData.error.code,
32
+ isUserFriendly: true,
33
+ };
34
+ }
35
+ } catch {
36
+ // Si le parsing JSON échoue, continuer avec le message d'erreur brut
37
+ }
38
+
39
+ // Vérifier les codes d'erreur dans le message
40
+ const errorMessage = error.message || "";
41
+ const detectedCode = detectErrorCode(errorMessage);
42
+ if (detectedCode) {
43
+ return {
44
+ message: getUserFriendlyMessage(detectedCode, errorMessage),
45
+ code: detectedCode,
46
+ isUserFriendly: true,
47
+ };
48
+ }
49
+ }
50
+
51
+ // Erreur générique
52
+ return {
53
+ message: "Une erreur est survenue lors de l'appel à l'API IA",
54
+ code: ErrorCode.UNKNOWN,
55
+ isUserFriendly: true,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Détecte le code d'erreur dans un message
61
+ */
62
+ function detectErrorCode(message: string): string | null {
63
+ const lowercaseMessage = message.toLowerCase();
64
+
65
+ if (lowercaseMessage.includes("insufficient_tokens") ||
66
+ lowercaseMessage.includes("insufficient balance") ||
67
+ lowercaseMessage.includes("not enough tokens")) {
68
+ return ErrorCode.INSUFFICIENT_TOKENS;
69
+ }
70
+
71
+ if (lowercaseMessage.includes("unauthorized") ||
72
+ lowercaseMessage.includes("invalid api key") ||
73
+ lowercaseMessage.includes("authentication failed")) {
74
+ return ErrorCode.UNAUTHORIZED;
75
+ }
76
+
77
+ if (lowercaseMessage.includes("model disabled") ||
78
+ lowercaseMessage.includes("model not available")) {
79
+ return ErrorCode.MODEL_DISABLED;
80
+ }
81
+
82
+ if (lowercaseMessage.includes("timeout") ||
83
+ lowercaseMessage.includes("timed out")) {
84
+ return ErrorCode.TIMEOUT;
85
+ }
86
+
87
+ if (lowercaseMessage.includes("network error") ||
88
+ lowercaseMessage.includes("fetch failed")) {
89
+ return ErrorCode.NETWORK_ERROR;
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ /**
96
+ * Génère un message d'erreur convivial selon le code d'erreur
97
+ */
98
+ function getUserFriendlyMessage(code: string, originalMessage: string): string {
99
+ switch (code) {
100
+ case ErrorCode.INSUFFICIENT_TOKENS:
101
+ return "Solde insuffisant. Veuillez recharger votre compte pour continuer.";
102
+
103
+ case ErrorCode.UNAUTHORIZED:
104
+ return "Clé API invalide ou expirée. Vérifiez votre configuration.";
105
+
106
+ case ErrorCode.MODEL_DISABLED:
107
+ return "Ce modèle n'est pas disponible actuellement.";
108
+
109
+ case ErrorCode.TIMEOUT:
110
+ return "La requête a pris trop de temps. Essayez à nouveau.";
111
+
112
+ case ErrorCode.NETWORK_ERROR:
113
+ return "Erreur de connexion. Vérifiez votre connexion internet.";
114
+
115
+ case ErrorCode.PROVIDER_DOWN:
116
+ return "Service temporairement indisponible. Essayez dans quelques instants.";
117
+
118
+ case ErrorCode.BAD_REQUEST:
119
+ return "Paramètres de requête invalides. Vérifiez votre configuration.";
120
+
121
+ case ErrorCode.NO_API_KEY:
122
+ return "Clé API manquante. Vérifiez votre configuration.";
123
+
124
+ case ErrorCode.PRICING_UNAVAILABLE:
125
+ return "Tarification indisponible pour ce modèle.";
126
+
127
+ default:
128
+ // Pour les erreurs inconnues, essayer d'extraire un message utile
129
+ if (originalMessage && !originalMessage.startsWith("HTTP ")) {
130
+ return originalMessage;
131
+ }
132
+ return "Une erreur inattendue s'est produite. Essayez à nouveau.";
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Interface pour la callback de gestion d'erreur uniforme
138
+ */
139
+ export interface ErrorToastCallback {
140
+ (toast: {
141
+ type: "error";
142
+ message: string;
143
+ code?: string;
144
+ }): void;
145
+ }
146
+
147
+ /**
148
+ * Gestionnaire d'erreur uniforme pour tous les composants AI
149
+ */
150
+ export function handleAIError(error: any, onToast?: ErrorToastCallback): void {
151
+ const parsedError = parseAIError(error);
152
+
153
+ console.error("[AI Error]", {
154
+ original: error,
155
+ parsed: parsedError,
156
+ });
157
+
158
+ onToast?.({
159
+ type: "error",
160
+ message: parsedError.message,
161
+ code: parsedError.code,
162
+ });
163
+ }