@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.
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiChipLabel.js +2 -0
- package/dist/components/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +2 -34
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +2 -5
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +5 -2
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +17 -7
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +2 -1
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +2 -1
- package/dist/hooks/useModelManagement.d.ts.map +1 -1
- package/dist/hooks/useModelManagement.js +24 -12
- package/dist/utils/errorHandler.d.ts +24 -0
- package/dist/utils/errorHandler.d.ts.map +1 -0
- package/dist/utils/errorHandler.js +122 -0
- package/package.json +2 -2
- package/src/components/AiChipLabel.tsx +2 -0
- package/src/components/AiContextButton.tsx +2 -34
- package/src/components/AiImageButton.tsx +2 -5
- package/src/components/AiInput.tsx +5 -2
- package/src/components/AiPromptPanel.tsx +15 -6
- package/src/components/AiSelect.tsx +2 -1
- package/src/components/AiTextarea.tsx +2 -1
- package/src/hooks/useModelManagement.ts +24 -12
- package/src/utils/errorHandler.ts +163 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiChipLabel.d.ts","sourceRoot":"","sources":["../../src/components/AiChipLabel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0C,MAAM,OAAO,CAAC;
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
|
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 =
|
|
50
|
-
?
|
|
51
|
-
|
|
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;
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
baseUrl:
|
|
19
|
-
apiKey:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
}
|